Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c1f7352
Remove generic `Stream` and `StreamError` types
MaxDesiatov Jan 18, 2026
e47a287
Address PR feedback
MaxDesiatov Jan 18, 2026
0d8625c
Adopt typed throws in `WasmParser` module
MaxDesiatov Jan 18, 2026
030d332
Apply formatter
MaxDesiatov Jan 18, 2026
ddd8d4b
Add `wasm2wat` function and CLI
MaxDesiatov Feb 22, 2026
3facabe
Fix CMake build
MaxDesiatov Feb 22, 2026
b0e0083
Run wasm2wat roundtrip verification on spec tests
MaxDesiatov Feb 22, 2026
7bc1522
Apply formatting
MaxDesiatov Feb 23, 2026
a6fc2be
Merge branch 'main' of github.com:swiftwasm/WasmKit into maxd/wasm2wat
MaxDesiatov Feb 23, 2026
5349586
WasmKitError WIP
MaxDesiatov Feb 24, 2026
da161f0
`ValidationError` unification with `WasmKitError`
MaxDesiatov Feb 24, 2026
90f4e24
Unified `TranslationError` with `WasmKitError`
MaxDesiatov Feb 24, 2026
f0b46c6
Fix error time mismatch in Translator/Validator
MaxDesiatov Feb 24, 2026
5ad1069
Merge branch 'main' of github.com:swiftwasm/WasmKit into maxd/univers…
MaxDesiatov Feb 24, 2026
53b3a72
Fix build errors after merge
MaxDesiatov Feb 24, 2026
a1f7681
Fix formatting
MaxDesiatov Feb 24, 2026
50118c6
Fix CMake build
MaxDesiatov Feb 25, 2026
9a38edc
Fix `ModuleParser` using `unclassified` with no need
MaxDesiatov Feb 25, 2026
834760f
Removing another `unclassified`
MaxDesiatov Feb 25, 2026
b116abe
Remove unused `DecoderError`
MaxDesiatov Feb 25, 2026
589d247
Remove unused typealiases
MaxDesiatov Feb 25, 2026
77ac03b
Clean up formatting
MaxDesiatov Feb 25, 2026
e820d3b
Merge branch 'maxd/universal-wasm-parser-error' of github.com:swiftwa…
MaxDesiatov Feb 25, 2026
15604f7
Codegen for most of `TextInstructionVisitor.swift`
MaxDesiatov Feb 25, 2026
b627ea6
Re-run WasmGen
MaxDesiatov Feb 25, 2026
5e8d558
Fix CMake build
MaxDesiatov Feb 27, 2026
0f0828c
Merge branch 'main' of github.com:swiftwasm/WasmKit into maxd/wasm2wat
MaxDesiatov Apr 17, 2026
930b432
Merge branch 'main' of github.com:swiftwasm/WasmKit into maxd/wasm2wat
MaxDesiatov Apr 17, 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
1 change: 1 addition & 0 deletions Sources/CLI/CLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct CLI: AsyncParsableCommand {
Explore.self,
Run.self,
Wat2wasm.self,
Wasm2wat.self,
]
)
}
49 changes: 49 additions & 0 deletions Sources/CLICommands/Wasm2wat.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import ArgumentParser
import SystemPackage
import WAT
import WasmParser

package struct Wasm2wat: ParsableCommand {
package static let configuration = CommandConfiguration(
commandName: "wasm2wat",
abstract: "Disassemble a WebAssembly binary into WebAssembly text format"
)

@Argument(help: "Path to a WebAssembly binary (`.wasm`) file to disassemble.")
var path: String

@Option(
name: [.short, .long],
help: """
Path to the output WebAssembly Text Format (`.wat`) file.
If not provided, the text is written to standard output.
"""
)
var output: String?

package init() {}

package func run() throws {
let filePath = FilePath(path)
let fileHandle = try FileDescriptor.open(filePath, .readOnly)
defer { try? fileHandle.close() }

let stream = try FileHandleStream(fileHandle: fileHandle)

let wat = try wasm2wat(stream)

if let outputPath = output {
let outPath = FilePath(outputPath)
let outHandle = try FileDescriptor.open(
outPath,
.writeOnly,
options: [.create, .truncate],
permissions: [.ownerReadWrite, .groupRead, .otherRead]
)
defer { try? outHandle.close() }
try outHandle.writeAll(Array(wat.utf8))
} else {
print(wat)
}
}
}
100 changes: 100 additions & 0 deletions Sources/WAT/TextFormat/ModuleCollector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import WasmParser
import WasmTypes

/// Collected information from all sections of a WebAssembly binary module.
struct ModuleInfo {
var types: [FunctionType] = []
var imports: [Import] = []
/// Type indices from the function section (one per local function).
var functionTypeIndices: [TypeIndex] = []
var tables: [Table] = []
var memories: [Memory] = []
var globals: [Global] = []
var exports: [Export] = []
var start: FunctionIndex? = nil
var elements: [ElementSegment] = []
/// Code bodies for local functions (parallel to functionTypeIndices).
var codes: [Code] = []
var data: [DataSegment] = []
/// Function names from the `name` custom section (index → name).
var functionNames: [UInt32: String] = [:]
/// Local variable names from the `name` custom section
/// (function index → (local index → name)).
var localNames: [UInt32: [UInt32: String]] = [:]

/// Total number of functions (imports + local definitions).
var functionCount: Int {
let importedFunctions = imports.filter {
if case .function = $0.descriptor { return true }
return false
}.count
return importedFunctions + functionTypeIndices.count
}

/// Number of imported functions (occupy the first N function indices).
var importedFunctionCount: Int {
imports.filter {
if case .function = $0.descriptor { return true }
return false
}.count
}
}

/// Parses a WebAssembly binary and collects all sections into a `ModuleInfo`.
///
/// - Parameters:
/// - bytes: The raw bytes of the WebAssembly binary.
/// - features: The feature set to use while parsing.
/// - Returns: A populated `ModuleInfo`.
func collectModule<Stream: ByteStream>(stream: Stream, features: WasmFeatureSet = .default) throws -> ModuleInfo {
var info = ModuleInfo()
var parser = WasmParser.Parser(stream: stream, features: features)
while let payload = try parser.parseNext() {
switch payload {
case .header:
break
case .typeSection(let types):
info.types = types
case .importSection(let imports):
info.imports = imports
case .functionSection(let indices):
info.functionTypeIndices = indices
case .tableSection(let tables):
info.tables = tables
case .memorySection(let memories):
info.memories = memories
case .globalSection(let globals):
info.globals = globals
case .exportSection(let exports):
info.exports = exports
case .startSection(let start):
info.start = start
case .elementSection(let elements):
info.elements = elements
case .codeSection(let codes):
info.codes = codes
case .dataSection(let data):
info.data = data
case .dataCount:
break
case .customSection(let section) where section.name == "name":
parseNameSection(section.bytes, into: &info)
case .customSection:
break
}
}
return info
}

// MARK: - Name section decoding

private func parseNameSection(_ bytes: ArraySlice<UInt8>, into info: inout ModuleInfo) {
let stream = StaticByteStream(bytes: bytes)
let nameParser = NameSectionParser(stream: stream)
guard let parsedNames = try? nameParser.parseAll() else { return }
for parsedName in parsedNames {
if case .functions(let nameMap) = parsedName {
info.functionNames = nameMap
}
}
}
Loading
Loading