Skip to content

Commit bab1f10

Browse files
authored
Merge pull request #3 from GoodNotes/code-wasm-compatibility
Project WASM compatibility first step
2 parents 0ddb743 + 9fec130 commit bab1f10

55 files changed

Lines changed: 3876 additions & 124 deletions

Some content is hidden

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

.github/workflows/wasm.yml

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,45 @@ name: wasm
22

33
on:
44
push:
5-
pull_request:
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"
612

713
jobs:
814
build:
9-
runs-on: macos-latest
10-
15+
runs-on: ubuntu-24.04
1116
steps:
1217
- uses: actions/checkout@v4
1318

14-
# Use Swift 6.0.3 to match the pinned SDK version in build-and-test-wasm.sh
15-
# The script will automatically install the 6.0.3-RELEASE WASM SDK
16-
- name: Install Swift
17-
uses: swift-actions/setup-swift@v2
18-
with:
19-
swift-version: "6.1"
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
2044

2145
# Wasmtime is required because `swift test` doesn't work for WebAssembly targets.
2246
# For WASM, we must build tests separately and run them with a WASM runtime.

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ let package = Package(
1212
],
1313
dependencies: [
1414
.package(url: "https://github.com/apple/swift-collections", from: "1.0.0"),
15-
.package(url: "https://github.com/GoodNotes/CLAPACK", branch: "eigen-support"),
15+
.package(url: "https://github.com/goodnotes/CLAPACK", branch: "eigen-support"),
1616
],
1717
targets: [
1818
.target(

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)

Sources/Matft/core/object/mftype.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
public enum MfType: Int{
1415
case None
@@ -65,6 +66,7 @@ public enum MfType: Int{
6566
return MfType.mftype(value: value as Any)
6667
}
6768

69+
#if canImport(CoreML)
6870
@available(macOS 10.13, *)
6971
@available(iOS 14.0, *)
7072
static internal func mftype(value: MLMultiArrayDataType) -> MfType{
@@ -77,6 +79,7 @@ public enum MfType: Int{
7779
return .Object // Not supported
7880
}
7981
}
82+
#endif
8083

8184
static public func priority(_ a: MfType, _ b: MfType) -> MfType{
8285
if a.rawValue < b.rawValue{

Sources/Matft/core/protocol/mfdataProtocol.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
//
77

88
import Foundation
9+
#if canImport(CoreML)
910
import CoreML
11+
#endif
1012

1113
public protocol MfDataBasable {}
1214

1315
extension MfData: MfDataBasable{}
1416

17+
#if canImport(CoreML)
1518
@available(macOS 10.13, *)
1619
@available(iOS 14.0, *)
1720
extension MLMultiArray: MfDataBasable{}
21+
#endif

Sources/Matft/core/protocol/mftypeProtocol.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,53 @@
77
//
88

99
import Foundation
10+
#if canImport(Accelerate)
1011
import Accelerate
12+
#else
13+
/// Fallback complex type for non-Apple platforms (split complex format for Float)
14+
public struct DSPSplitComplex {
15+
public var realp: UnsafeMutablePointer<Float>
16+
public var imagp: UnsafeMutablePointer<Float>
17+
18+
public init(realp: UnsafeMutablePointer<Float>, imagp: UnsafeMutablePointer<Float>) {
19+
self.realp = realp
20+
self.imagp = imagp
21+
}
22+
}
23+
24+
/// Fallback complex type for non-Apple platforms (split complex format for Double)
25+
public struct DSPDoubleSplitComplex {
26+
public var realp: UnsafeMutablePointer<Double>
27+
public var imagp: UnsafeMutablePointer<Double>
28+
29+
public init(realp: UnsafeMutablePointer<Double>, imagp: UnsafeMutablePointer<Double>) {
30+
self.realp = realp
31+
self.imagp = imagp
32+
}
33+
}
34+
35+
/// Fallback complex type for non-Apple platforms (interleaved complex format for Float)
36+
public struct DSPComplex {
37+
public var real: Float
38+
public var imag: Float
39+
40+
public init(real: Float, imag: Float) {
41+
self.real = real
42+
self.imag = imag
43+
}
44+
}
45+
46+
/// Fallback complex type for non-Apple platforms (interleaved complex format for Double)
47+
public struct DSPDoubleComplex {
48+
public var real: Double
49+
public var imag: Double
50+
51+
public init(real: Double, imag: Double) {
52+
self.real = real
53+
self.imag = imag
54+
}
55+
}
56+
#endif
1157
/*
1258
public protocol MfTypable: Numeric{}
1359

0 commit comments

Comments
 (0)