From dbc1b073ca33b8fb2389e41edef95e6bdfd9e8a1 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 29 Jun 2026 22:09:49 +0100 Subject: [PATCH] WasmParser: Add `FileSystem` trait to allow omitting file system helpers Extracted from https://github.com/swiftwasm/WasmKit/pull/357 Co-Authored-By: Joannis Orlandos --- .github/workflows/main.yml | 16 +- CMakeLists.txt | 1 + Package.swift | 8 +- Sources/WasmKit/CMakeLists.txt | 6 + .../Execution/ParsedComponentBuilder.swift | 29 +-- Sources/WasmKit/ModuleParser+FileSystem.swift | 38 ++++ Sources/WasmKit/ModuleParser.swift | 35 ---- Sources/WasmParser/CMakeLists.txt | 10 +- .../WasmParser/Stream/FileHandleStream.swift | 170 +++++++++++------- Sources/WasmParser/WasmParser.swift | 37 ---- 10 files changed, 189 insertions(+), 161 deletions(-) create mode 100644 Sources/WasmKit/ModuleParser+FileSystem.swift diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 00ad14a2..af8578d8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -61,7 +61,7 @@ jobs: ./Vendor/checkout-dependency --category component-model swift --version - swift test --sanitize address --traits ComponentModel,WasmDebuggingSupport ${{ matrix.test-args }} + swift test --sanitize address --traits FileSystem,ComponentModel,WasmDebuggingSupport ${{ matrix.test-args }} swift build --package-path Benchmarks build-macos: @@ -71,7 +71,7 @@ jobs: # Swift 6.3.2 - os: macos-26 xcode: Xcode_26.5 - test-args: "--sanitize address --traits ComponentModel,WasmDebuggingSupport" + test-args: "--sanitize address --traits FileSystem,ComponentModel,WasmDebuggingSupport" runs-on: ${{ matrix.os }} name: "build-macos (${{ matrix.xcode }})" @@ -132,15 +132,15 @@ jobs: matrix: include: - swift: "swift:6.3-noble" - test-args: "--traits ComponentModel,WasmDebuggingSupport --enable-code-coverage" + test-args: "--traits FileSystem,ComponentModel,WasmDebuggingSupport --enable-code-coverage" build-benchmarks: true run-benchmarks: true build-dev-dashboard: true - swift: "swiftlang/swift:nightly-6.4.x-noble" - test-args: "--traits ComponentModel,WasmDebuggingSupport" + test-args: "--traits FileSystem,ComponentModel,WasmDebuggingSupport" build-benchmarks: true - swift: "swiftlang/swift:nightly-main-noble" - test-args: "--traits ComponentModel,WasmDebuggingSupport" + test-args: "--traits FileSystem,ComponentModel,WasmDebuggingSupport" build-benchmarks: true runs-on: ubuntu-24.04 @@ -234,9 +234,9 @@ jobs: run: ./build-exec swift sdk install "${{ matrix.musl-swift-sdk-download }}" --checksum "${{ matrix.musl-swift-sdk-checksum }}" - name: Build (x86_64-swift-linux-musl) - run: ./build-exec swift build --swift-sdk x86_64-swift-linux-musl --traits ComponentModel,WasmDebuggingSupport --explicit-target-dependency-import-check error + run: ./build-exec swift build --swift-sdk x86_64-swift-linux-musl --traits FileSystem,ComponentModel,WasmDebuggingSupport --explicit-target-dependency-import-check error - name: Build (aarch64-swift-linux-musl) - run: ./build-exec swift build --swift-sdk aarch64-swift-linux-musl --traits ComponentModel,WasmDebuggingSupport --explicit-target-dependency-import-check error + run: ./build-exec swift build --swift-sdk aarch64-swift-linux-musl --traits FileSystem,ComponentModel,WasmDebuggingSupport --explicit-target-dependency-import-check error build-android: runs-on: ubuntu-24.04 @@ -297,4 +297,4 @@ jobs: - name: Install Swift SDK run: swift sdk install https://download.swift.org/swift-6.3.2-release/wasm-sdk/swift-6.3.2-RELEASE/swift-6.3.2-RELEASE_wasm.artifactbundle.tar.gz --checksum a61f0584c93283589f8b2f42db05c1f9a182b506c2957271402992655591dd7c - name: Build with the Swift SDK - run: swift build --swift-sdk "$(swiftc -print-target-info | jq -r '.swiftCompilerTag')_wasm" --traits ComponentModel --explicit-target-dependency-import-check error --product wasmkit-cli + run: swift build --swift-sdk "$(swiftc -print-target-info | jq -r '.swiftCompilerTag')_wasm" --traits FileSystem,ComponentModel --explicit-target-dependency-import-check error --product wasmkit-cli diff --git a/CMakeLists.txt b/CMakeLists.txt index 048419d2..46519281 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,7 @@ add_compile_definitions( include(FetchContent) option(WASMKIT_BUILD_CLI "Build wasmkit-cli" ON) +option(WASMKIT_ENABLE_FILESYSTEM "Enable FileSystem support" ON) if(WASMKIT_BUILD_CLI) set(BUILD_TESTING OFF) # disable ArgumentParser tests diff --git a/Package.swift b/Package.swift index b100496a..f4e3e8bc 100644 --- a/Package.swift +++ b/Package.swift @@ -38,7 +38,8 @@ let package = Package( .library(name: "_CabiShims", targets: ["_CabiShims"]), ], traits: [ - .default(enabledTraits: []), + .default(enabledTraits: ["FileSystem"]), + "FileSystem", "ComponentModel", "WasmDebuggingSupport", ], @@ -112,7 +113,10 @@ let package = Package( name: "WasmParser", dependencies: [ "WasmTypes", - .product(name: "SystemPackage", package: "swift-system"), + .product( + name: "SystemPackage", package: "swift-system", + condition: .when(traits: ["FileSystem"]) + ), .target( name: "ComponentModel", condition: .when(traits: ["ComponentModel"]) diff --git a/Sources/WasmKit/CMakeLists.txt b/Sources/WasmKit/CMakeLists.txt index 2f1e4cc1..fb848b43 100644 --- a/Sources/WasmKit/CMakeLists.txt +++ b/Sources/WasmKit/CMakeLists.txt @@ -3,6 +3,7 @@ add_wasmkit_library(WasmKit Imports.swift Module.swift ModuleParser.swift + ModuleParser+FileSystem.swift SIMDOpcode.swift Translator.swift Validator.swift @@ -47,5 +48,10 @@ add_wasmkit_library(WasmKit Execution/V128Storage.swift ) +if(WASMKIT_ENABLE_FILESYSTEM) + target_compile_options(WasmKit PRIVATE + $<$:-D;FileSystem>) +endif() + target_link_wasmkit_libraries(WasmKit PUBLIC _CWasmKit WasmParser SystemExtras WasmTypes SystemPackage) diff --git a/Sources/WasmKit/Execution/ParsedComponentBuilder.swift b/Sources/WasmKit/Execution/ParsedComponentBuilder.swift index aa1bd765..2d028195 100644 --- a/Sources/WasmKit/Execution/ParsedComponentBuilder.swift +++ b/Sources/WasmKit/Execution/ParsedComponentBuilder.swift @@ -1,22 +1,9 @@ #if ComponentModel import ComponentModel - import SystemPackage import WasmParser // MARK: - Component Parsing - /// Parse a component binary file from a caller-owned file descriptor. - /// - /// The descriptor is consumed from its current offset and is not closed by - /// this function. - public func parseComponent( - fileHandle: FileDescriptor, - features: WasmFeatureSet = .default - ) throws -> ParsedComponent { - let stream = try FileHandleStream(fileHandle: fileHandle) - return try parseComponent(stream: stream, features: features) - } - /// Parse a component binary into a `ParsedComponent` ready for instantiation. /// /// This function converts the streaming `ComponentParser` output into a @@ -609,3 +596,19 @@ } #endif + +#if ComponentModel && FileSystem + import struct SystemPackage.FileDescriptor + + /// Parse a component binary file from a caller-owned file descriptor. + /// + /// The descriptor is consumed from its current offset and is not closed by + /// this function. + public func parseComponent( + fileHandle: FileDescriptor, + features: WasmFeatureSet = .default + ) throws -> ParsedComponent { + let stream = try FileHandleStream(fileHandle: fileHandle) + return try parseComponent(stream: stream, features: features) + } +#endif diff --git a/Sources/WasmKit/ModuleParser+FileSystem.swift b/Sources/WasmKit/ModuleParser+FileSystem.swift new file mode 100644 index 00000000..bcc49fd3 --- /dev/null +++ b/Sources/WasmKit/ModuleParser+FileSystem.swift @@ -0,0 +1,38 @@ +#if FileSystem + import WasmParser + import SystemExtras + import SystemPackage + + #if os(Windows) + import ucrt + #endif + + /// Parse a given file as a WebAssembly binary format file + /// > Note: + public func parseWasm(filePath: FilePath, features: WasmFeatureSet = .default) throws -> Module { + #if os(Windows) + // TODO: Upstream `O_BINARY` to `SystemPackage + let accessMode = FileDescriptor.AccessMode( + rawValue: FileDescriptor.AccessMode.readOnly.rawValue | O_BINARY + ) + #else + let accessMode: FileDescriptor.AccessMode = .readOnly + #endif + let fileHandle = try FileDescriptor.open(filePath, accessMode) + return try withThrowing { + try parseWasm(fileHandle: fileHandle, features: features) + } defer: { + try fileHandle.close() + } + } + + /// Parse a WebAssembly binary file from a caller-owned file descriptor. + /// + /// The descriptor is consumed from its current offset and is not closed by + /// this function. + public func parseWasm(fileHandle: FileDescriptor, features: WasmFeatureSet = .default) throws -> Module { + let stream = try FileHandleStream(fileHandle: fileHandle) + let module = try parseModule(stream: stream, features: features) + return module + } +#endif diff --git a/Sources/WasmKit/ModuleParser.swift b/Sources/WasmKit/ModuleParser.swift index b2ed5e17..7a4965f6 100644 --- a/Sources/WasmKit/ModuleParser.swift +++ b/Sources/WasmKit/ModuleParser.swift @@ -1,40 +1,5 @@ -import SystemExtras -import SystemPackage import WasmParser -#if os(Windows) - import ucrt -#endif - -/// Parse a given file as a WebAssembly binary format file -/// > Note: -public func parseWasm(filePath: FilePath, features: WasmFeatureSet = .default) throws -> Module { - #if os(Windows) - // TODO: Upstream `O_BINARY` to `SystemPackage - let accessMode = FileDescriptor.AccessMode( - rawValue: FileDescriptor.AccessMode.readOnly.rawValue | O_BINARY - ) - #else - let accessMode: FileDescriptor.AccessMode = .readOnly - #endif - let fileHandle = try FileDescriptor.open(filePath, accessMode) - return try withThrowing { - try parseWasm(fileHandle: fileHandle, features: features) - } defer: { - try fileHandle.close() - } -} - -/// Parse a WebAssembly binary file from a caller-owned file descriptor. -/// -/// The descriptor is consumed from its current offset and is not closed by -/// this function. -public func parseWasm(fileHandle: FileDescriptor, features: WasmFeatureSet = .default) throws -> Module { - let stream = try FileHandleStream(fileHandle: fileHandle) - let module = try parseModule(stream: stream, features: features) - return module -} - /// Parse a given byte array as a WebAssembly binary format file /// > Note: public func parseWasm(bytes: [UInt8], features: WasmFeatureSet = .default) throws(WasmKitError) -> Module { diff --git a/Sources/WasmParser/CMakeLists.txt b/Sources/WasmParser/CMakeLists.txt index a0106f20..73827adc 100644 --- a/Sources/WasmParser/CMakeLists.txt +++ b/Sources/WasmParser/CMakeLists.txt @@ -11,4 +11,12 @@ add_wasmkit_library(WasmParser ) target_link_wasmkit_libraries(WasmParser PUBLIC - SystemExtras WasmTypes SystemPackage) + WasmTypes) + +if(WASMKIT_ENABLE_FILESYSTEM) + target_compile_options(WasmParser PRIVATE + $<$:-D;FileSystem>) + target_link_wasmkit_libraries(WasmParser PUBLIC + SystemPackage) +endif() + diff --git a/Sources/WasmParser/Stream/FileHandleStream.swift b/Sources/WasmParser/Stream/FileHandleStream.swift index 0bc954f1..35500b2c 100644 --- a/Sources/WasmParser/Stream/FileHandleStream.swift +++ b/Sources/WasmParser/Stream/FileHandleStream.swift @@ -1,92 +1,132 @@ -import struct SystemPackage.FileDescriptor +// "FileSystem" trait can be turned off to support embedded platforms +#if FileSystem -public final class FileHandleStream: ByteStream { - private(set) public var currentIndex: Int = 0 + import struct SystemPackage.FileDescriptor + import struct SystemPackage.FilePath - private let fileHandle: FileDescriptor - private let bufferLength: Int + #if os(Windows) + import ucrt + #endif - private var endOffset: Int = 0 - private var startOffset: Int = 0 - private var bytes: [UInt8] = [] + extension Parser where Stream == FileHandleStream { - public init(fileHandle: FileDescriptor, bufferLength: Int = 1024 * 8) throws { - self.fileHandle = fileHandle - self.bufferLength = bufferLength + /// Initialize a new parser with the given file handle + /// + /// - Parameters: + /// - fileHandle: The file handle to the WebAssembly binary file to parse + /// - features: Enabled WebAssembly features for parsing + public init(fileHandle: FileDescriptor, features: WasmFeatureSet = .default) throws { + self.init(stream: try FileHandleStream(fileHandle: fileHandle), features: features) + } - try readMoreIfNeeded() + /// Initialize a new parser with the given file path + /// + /// - Parameters: + /// - filePath: The file path to the WebAssembly binary file to parse + /// - features: Enabled WebAssembly features for parsing + public init(filePath: FilePath, features: WasmFeatureSet = .default) throws { + #if os(Windows) + // TODO: Upstream `O_BINARY` to `SystemPackage + let accessMode = FileDescriptor.AccessMode( + rawValue: FileDescriptor.AccessMode.readOnly.rawValue | O_BINARY + ) + #else + let accessMode: FileDescriptor.AccessMode = .readOnly + #endif + let fileHandle = try FileDescriptor.open(filePath, accessMode) + self.init(stream: try FileHandleStream(fileHandle: fileHandle), features: features) + } } - private func readMoreIfNeeded() throws(WasmParserError) { - guard Int(endOffset) == currentIndex else { return } - startOffset = currentIndex + public final class FileHandleStream: ByteStream { + private(set) public var currentIndex: Int = 0 - do { - let data = try fileHandle.read(upToCount: bufferLength) + private let fileHandle: FileDescriptor + private let bufferLength: Int - bytes = [UInt8](data) - } catch { - throw WasmParserError("I/O error: \(error)", offset: currentIndex) - } - endOffset = startOffset + bytes.count - } + private var endOffset: Int = 0 + private var startOffset: Int = 0 + private var bytes: [UInt8] = [] + + public init(fileHandle: FileDescriptor, bufferLength: Int = 1024 * 8) throws { + self.fileHandle = fileHandle + self.bufferLength = bufferLength - @discardableResult - public func consumeAny() throws(WasmParserError) -> UInt8 { - guard let consumed = try peek() else { - throw WasmParserError(message: .unexpectedEnd, offset: currentIndex) + try readMoreIfNeeded() } - currentIndex = bytes.index(after: currentIndex) - return consumed - } - public func consume(count: Int) throws(WasmParserError) -> ArraySlice { - let bytesToRead = currentIndex + count - endOffset + private func readMoreIfNeeded() throws(WasmParserError) { + guard Int(endOffset) == currentIndex else { return } + startOffset = currentIndex - guard bytesToRead > 0 else { - let bytesIndex = currentIndex - startOffset - let result = bytes[bytesIndex.. UInt8 { + guard let consumed = try peek() else { + throw WasmParserError(message: .unexpectedEnd, offset: currentIndex) + } + currentIndex = bytes.index(after: currentIndex) + return consumed } - bytes.append(contentsOf: [UInt8](data)) - endOffset = endOffset + data.count + public func consume(count: Int) throws(WasmParserError) -> ArraySlice { + let bytesToRead = currentIndex + count - endOffset + + guard bytesToRead > 0 else { + let bytesIndex = currentIndex - startOffset + let result = bytes[bytesIndex.. UInt8? { - try readMoreIfNeeded() + public func peek() throws(WasmParserError) -> UInt8? { + try readMoreIfNeeded() - let index = currentIndex - startOffset - guard bytes.indices.contains(index) else { - return nil - } + let index = currentIndex - startOffset + guard bytes.indices.contains(index) else { + return nil + } - return bytes[index] + return bytes[index] + } } -} -extension FileDescriptor { - fileprivate func read(upToCount maxLength: Int) throws -> [UInt8] { - try [UInt8](unsafeUninitializedCapacity: maxLength) { buffer, outCount in - outCount = try read(into: UnsafeMutableRawBufferPointer(buffer)) + extension FileDescriptor { + fileprivate func read(upToCount maxLength: Int) throws -> [UInt8] { + try [UInt8](unsafeUninitializedCapacity: maxLength) { buffer, outCount in + outCount = try read(into: UnsafeMutableRawBufferPointer(buffer)) + } } } -} + +#endif diff --git a/Sources/WasmParser/WasmParser.swift b/Sources/WasmParser/WasmParser.swift index 3aa973aa..c9a8917f 100644 --- a/Sources/WasmParser/WasmParser.swift +++ b/Sources/WasmParser/WasmParser.swift @@ -1,12 +1,5 @@ import WasmTypes -import struct SystemPackage.FileDescriptor -import struct SystemPackage.FilePath - -#if os(Windows) - import ucrt -#endif - /// A streaming parser for WebAssembly binary format. /// /// The parser is designed to be used to incrementally parse a WebAssembly binary bytestream. @@ -54,36 +47,6 @@ extension Parser where Stream == StaticByteStream { } } -extension Parser where Stream == FileHandleStream { - - /// Initialize a new parser with the given file handle - /// - /// - Parameters: - /// - fileHandle: The file handle to the WebAssembly binary file to parse - /// - features: Enabled WebAssembly features for parsing - public init(fileHandle: FileDescriptor, features: WasmFeatureSet = .default) throws { - self.init(stream: try FileHandleStream(fileHandle: fileHandle), features: features) - } - - /// Initialize a new parser with the given file path - /// - /// - Parameters: - /// - filePath: The file path to the WebAssembly binary file to parse - /// - features: Enabled WebAssembly features for parsing - public init(filePath: FilePath, features: WasmFeatureSet = .default) throws { - #if os(Windows) - // TODO: Upstream `O_BINARY` to `SystemPackage - let accessMode = FileDescriptor.AccessMode( - rawValue: FileDescriptor.AccessMode.readOnly.rawValue | O_BINARY - ) - #else - let accessMode: FileDescriptor.AccessMode = .readOnly - #endif - let fileHandle = try FileDescriptor.open(filePath, accessMode) - self.init(stream: try FileHandleStream(fileHandle: fileHandle), features: features) - } -} - @_documentation(visibility: internal) public struct ExpressionParser { /// The byte offset of the code in the module