Skip to content
2 changes: 2 additions & 0 deletions FuzzTesting/Sources/FuzzDifferential/FuzzDifferential.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ extension ValueType {
return .ref(.function(nil))
case .abstract(.externRef):
return .ref(.extern(nil))
case .abstract(.exnRef):
return .ref(.exception(nil))
case .concrete:
// We don't model GC reference heap types yet; use a null externref.
return .ref(.extern(nil))
Expand Down
2 changes: 2 additions & 0 deletions FuzzTesting/Sources/FuzzExecute/FuzzExecute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ extension ValueType {
return .ref(.function(nil))
case .abstract(.externRef):
return .ref(.extern(nil))
case .abstract(.exnRef):
return .ref(.exception(nil))
case .concrete:
// We don't model GC reference heap types yet; use a null externref.
return .ref(.extern(nil))
Expand Down
4 changes: 4 additions & 0 deletions FuzzTesting/Sources/WasmKitFuzzing/WasmKitFuzzing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public func fuzzInstantiation(bytes: [UInt8]) throws {
value = try Memory(store: store, type: memoryType)
case .table(let tableType):
value = try Table(store: store, type: tableType)
case .tag(let typeIndex):
guard typeIndex < module.types.count else { return }
let type = module.types[Int(typeIndex)]
value = Tag(store: store, type: type)
}
imports.define(module: importEntry.module, name: importEntry.name, value.externalValue)
}
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Proposals are grouped by their [phase](https://github.com/WebAssembly/meetings/b
| Proposal | Status | WasmKit version |
|----------|--------|-----------------|
| [Bulk Memory Operations](https://github.com/WebAssembly/bulk-memory-operations) | ✅ Implemented | [0.0.2] |
| [Exception Handling](https://github.com/WebAssembly/exception-handling) | ✅ Implemented | `main` branch |
| [Fixed-width SIMD](https://github.com/webassembly/simd) | ✅ Implemented | `main` branch |
| [Import/Export of Mutable Globals](https://github.com/WebAssembly/mutable-global) | ✅ Implemented | [0.0.2] |
| [Memory64](https://github.com/WebAssembly/memory64) | ✅ Implemented | [0.0.2] |
Expand All @@ -102,7 +103,6 @@ Proposals are grouped by their [phase](https://github.com/WebAssembly/meetings/b
| [Typed Function References](https://github.com/WebAssembly/function-references) | 🚧 Parser implemented | [0.2.0] |
| [Branch Hinting](https://github.com/WebAssembly/branch-hinting) | ❌ Not implemented | |
| [Custom Annotation Syntax in the Text Format](https://github.com/WebAssembly/annotations) | ❌ Not implemented | |
| [Exception Handling](https://github.com/WebAssembly/exception-handling) | ❌ Not implemented | |
| [Extended Constant Expressions](https://github.com/WebAssembly/extended-const) | ❌ Not implemented | |
| [Garbage Collection](https://github.com/WebAssembly/gc) | ❌ Not implemented | |
| [Multiple Memories](https://github.com/WebAssembly/multi-memory) | ❌ Not implemented | |
Expand Down
11 changes: 11 additions & 0 deletions Sources/WAT/BinaryEncoding/BinaryInstructionEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ protocol BinaryInstructionEncoder: InstructionVisitor {
mutating func encodeImmediates(memory: UInt32) throws(VisitorError)
mutating func encodeImmediates(relativeDepth: UInt32) throws(VisitorError)
mutating func encodeImmediates(table: UInt32) throws(VisitorError)
mutating func encodeImmediates(tagIndex: UInt32) throws(VisitorError)
mutating func encodeImmediates(targets: BrTable) throws(VisitorError)
mutating func encodeImmediates(type: HeapType) throws(VisitorError)
mutating func encodeImmediates(type: ValueType) throws(VisitorError)
Expand All @@ -33,6 +34,7 @@ protocol BinaryInstructionEncoder: InstructionVisitor {
mutating func encodeImmediates(value: Int32) throws(VisitorError)
mutating func encodeImmediates(value: Int64) throws(VisitorError)
mutating func encodeImmediates(value: V128) throws(VisitorError)
mutating func encodeImmediates(blockType: BlockType, tryCatch: TryCatch) throws(VisitorError)
mutating func encodeImmediates(dstMem: UInt32, srcMem: UInt32) throws(VisitorError)
mutating func encodeImmediates(dstTable: UInt32, srcTable: UInt32) throws(VisitorError)
mutating func encodeImmediates(elemIndex: UInt32, table: UInt32) throws(VisitorError)
Expand All @@ -57,6 +59,11 @@ extension BinaryInstructionEncoder {
try encodeImmediates(blockType: blockType)
}
mutating func visitElse() throws(VisitorError) { try encodeInstruction([0x05]) }
mutating func visitThrow(tagIndex: UInt32) throws(VisitorError) {
try encodeInstruction([0x08])
try encodeImmediates(tagIndex: tagIndex)
}
mutating func visitThrowRef() throws(VisitorError) { try encodeInstruction([0x0A]) }
mutating func visitEnd() throws(VisitorError) { try encodeInstruction([0x0B]) }
mutating func visitBr(relativeDepth: UInt32) throws(VisitorError) {
try encodeInstruction([0x0C])
Expand Down Expand Up @@ -95,6 +102,10 @@ extension BinaryInstructionEncoder {
try encodeInstruction([0x15])
try encodeImmediates(typeIndex: typeIndex)
}
mutating func visitTryTable(blockType: BlockType, tryCatch: TryCatch) throws(VisitorError) {
try encodeInstruction([0x1F])
try encodeImmediates(blockType: blockType, tryCatch: tryCatch)
}
mutating func visitDrop() throws(VisitorError) { try encodeInstruction([0x1A]) }
mutating func visitSelect() throws(VisitorError) { try encodeInstruction([0x1B]) }
mutating func visitTypedSelect(type: ValueType) throws(VisitorError) {
Expand Down
52 changes: 52 additions & 0 deletions Sources/WAT/BinaryEncoding/Encoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ extension ReferenceType: WasmEncodable {
// Use short form when available
case (true, .externRef): encoder.output.append(0x6F)
case (true, .funcRef): encoder.output.append(0x70)
case (true, .exnRef): encoder.output.append(0x69)
default:
encoder.output.append(isNullable ? 0x63 : 0x64)
encoder.encode(heapType)
Expand All @@ -143,6 +144,7 @@ extension HeapType: WasmEncodable {
switch self {
case .abstract(.externRef): encoder.output.append(0x6F)
case .abstract(.funcRef): encoder.output.append(0x70)
case .abstract(.exnRef): encoder.output.append(0x69)
case .concrete(let typeIndex):
// Note that the typeIndex is decoded as s33,
// so we need to encode it as signed.
Expand Down Expand Up @@ -338,6 +340,9 @@ extension Export: WasmEncodable {
case .global(let index):
encoder.output.append(0x03)
encoder.writeUnsignedLEB128(UInt32(index))
case .tag(let index):
encoder.output.append(0x04)
encoder.writeUnsignedLEB128(UInt32(index))
}
}
}
Expand Down Expand Up @@ -409,6 +414,10 @@ extension Import: WasmEncodable {
case .global(let globalType):
encoder.output.append(0x03)
globalType.encode(to: &encoder)
case .tag(let typeIndex):
encoder.output.append(0x04)
encoder.output.append(0x00) // attribute: exception
encoder.writeUnsignedLEB128(UInt32(typeIndex))
}
}
}
Expand Down Expand Up @@ -549,6 +558,29 @@ struct ExpressionEncoder: BinaryInstructionEncoder {
mutating func encodeImmediates(value: WasmTypes.V128) { encoder.output.append(contentsOf: value.bytes) }
mutating func encodeImmediates(value: WasmParser.IEEE754.Float32) { encodeFixedWidth(value.bitPattern) }
mutating func encodeImmediates(value: WasmParser.IEEE754.Float64) { encodeFixedWidth(value.bitPattern) }
mutating func encodeImmediates(tagIndex: UInt32) { encodeUnsigned(tagIndex) }
mutating func encodeImmediates(blockType: WasmParser.BlockType, tryCatch: WasmParser.TryCatch) {
encodeImmediates(blockType: blockType)
encoder.writeUnsignedLEB128(UInt32(tryCatch.catches.count))
for clause in tryCatch.catches {
switch clause {
case .catch(let tagIndex, let labelIndex):
encoder.output.append(0x00)
encoder.writeUnsignedLEB128(tagIndex)
encoder.writeUnsignedLEB128(labelIndex)
case .catchRef(let tagIndex, let labelIndex):
encoder.output.append(0x01)
encoder.writeUnsignedLEB128(tagIndex)
encoder.writeUnsignedLEB128(labelIndex)
case .catchAll(let labelIndex):
encoder.output.append(0x02)
encoder.writeUnsignedLEB128(labelIndex)
case .catchAllRef(let labelIndex):
encoder.output.append(0x03)
encoder.writeUnsignedLEB128(labelIndex)
}
}
}
mutating func encodeImmediates(dstMem: UInt32, srcMem: UInt32) {
encodeUnsigned(dstMem)
encodeUnsigned(srcMem)
Expand Down Expand Up @@ -579,6 +611,7 @@ func encode(module: inout Wat, options: EncodeOptions) throws(WatParserError) ->
return (locals, function)
}
var functionSection: [UInt32] = []
var tagSection: [UInt32] = []
var hasDataSegmentInstruction = false

if !functions.isEmpty {
Expand Down Expand Up @@ -613,6 +646,13 @@ func encode(module: inout Wat, options: EncodeOptions) throws(WatParserError) ->
}
}

// Pre-resolve tag type indices so their types are in the type section.
let tagDefinitions = module.tagsMap.definitions()
for tag in tagDefinitions {
let typeIndex = try module.types.resolveIndex(use: tag.typeUse)
tagSection.append(UInt32(typeIndex))
}

// Section 1: Type section
if !module.types.isEmpty {
encoder.section(id: 0x01) { encoder in
Expand Down Expand Up @@ -654,6 +694,18 @@ func encode(module: inout Wat, options: EncodeOptions) throws(WatParserError) ->
}
}

// Section 13: Tag section
// Note: tagsec is placed between memsec and globalsec in the module grammar.
// https://webassembly.github.io/exception-handling/core/binary/modules.html#binary-module
if !tagSection.isEmpty {
encoder.section(id: 0x0D) { encoder in
encoder.encodeVector(tagSection) { typeIndex, encoder in
encoder.output.append(0x00) // attribute: exception
encoder.writeUnsignedLEB128(typeIndex)
}
}
}

// Section 6: Global section
let globals = module.globals.definitions()
if !globals.isEmpty {
Expand Down
7 changes: 7 additions & 0 deletions Sources/WAT/ParseTextInstruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ func parseTextInstruction<V: InstructionVisitor>(
let (blockType) = try expressionParser.visitIf(wat: &wat)
return { visitor throws(WatParserError) in return try visitor.visitIf(blockType: blockType) }
case "else": return { visitor throws(WatParserError) in return try visitor.visitElse() }
case "throw":
let (tagIndex) = try expressionParser.visitThrow(wat: &wat)
return { visitor throws(WatParserError) in return try visitor.visitThrow(tagIndex: tagIndex) }
case "throw_ref": return { visitor throws(WatParserError) in return try visitor.visitThrowRef() }
case "end": return { visitor throws(WatParserError) in return try visitor.visitEnd() }
case "br":
let (relativeDepth) = try expressionParser.visitBr(wat: &wat)
Expand Down Expand Up @@ -59,6 +63,9 @@ func parseTextInstruction<V: InstructionVisitor>(
case "return_call_ref":
let (typeIndex) = try expressionParser.visitReturnCallRef(wat: &wat)
return { visitor throws(WatParserError) in return try visitor.visitReturnCallRef(typeIndex: typeIndex) }
case "try_table":
let (blockType, tryCatch) = try expressionParser.visitTryTable(wat: &wat)
return { visitor throws(WatParserError) in return try visitor.visitTryTable(blockType: blockType, tryCatch: tryCatch) }
case "drop": return { visitor throws(WatParserError) in return try visitor.visitDrop() }
case "select": return { visitor throws(WatParserError) in return try visitor.visitSelect() }
case "local.get":
Expand Down
47 changes: 44 additions & 3 deletions Sources/WAT/Parser/ExpressionParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -328,10 +328,10 @@ struct ExpressionParser<Visitor: InstructionVisitor> where Visitor.VisitorError
this.labelStack.pop()
return try visitor.visitEnd()
})
case "block", "loop":
case "block", "loop", "try_table":
// Visit the block instruction itself
_ = try visit(&visitor)
// Visit child expr here because folded "block" and "loop"
// Visit child expr here because folded "block", "loop", and "try_table"
// allows unfolded child instructions unlike others.
try parse(visitor: &visitor, wat: &wat)
suspense = Suspense(visit: { visitor, this throws(WatParserError) in
Expand Down Expand Up @@ -462,11 +462,13 @@ struct ExpressionParser<Visitor: InstructionVisitor> where Visitor.VisitorError
return .funcRef
} else if try parser.takeKeyword("extern") {
return .externRef
} else if try parser.takeKeyword("exn") {
return .exnRef
} else if let id = try parser.takeIndexOrId() {
let (_, index) = try wat.types.resolve(use: id)
return .concrete(typeIndex: UInt32(index))
}
throw WatParserError("expected \"func\", \"extern\" or type index", location: parser.lexer.location())
throw WatParserError("expected \"func\", \"extern\", \"exn\" or type index", location: parser.lexer.location())
}

private mutating func memArg(defaultAlign: UInt32) throws(WatParserError) -> MemArg {
Expand Down Expand Up @@ -545,6 +547,45 @@ extension ExpressionParser {
mutating func visitReturnCallRef(wat: inout Wat) throws(WatParserError) -> UInt32 {
return try visitCallRef(wat: &wat)
}
mutating func visitThrow(wat: inout Wat) throws(WatParserError) -> UInt32 {
let use = try parser.expectIndexOrId()
return UInt32(try wat.tagsMap.resolve(use: use).index)
}
mutating func visitTryTable(wat: inout Wat) throws(WatParserError) -> (blockType: BlockType, tryCatch: TryCatch) {
let label = try parser.takeId()
let bt = try blockType(wat: &wat)
var catches: [CatchClause] = []
while true {
if try parser.takeParenBlockStart("catch") {
let tagUse = try parser.expectIndexOrId()
let tagIndex = UInt32(try wat.tagsMap.resolve(use: tagUse).index)
let label = try labelIndex()
try parser.expect(.rightParen)
catches.append(.catch(tagIndex: tagIndex, labelIndex: label))
} else if try parser.takeParenBlockStart("catch_ref") {
let tagUse = try parser.expectIndexOrId()
let tagIndex = UInt32(try wat.tagsMap.resolve(use: tagUse).index)
let label = try labelIndex()
try parser.expect(.rightParen)
catches.append(.catchRef(tagIndex: tagIndex, labelIndex: label))
} else if try parser.takeParenBlockStart("catch_all") {
let label = try labelIndex()
try parser.expect(.rightParen)
catches.append(.catchAll(labelIndex: label))
} else if try parser.takeParenBlockStart("catch_all_ref") {
let label = try labelIndex()
try parser.expect(.rightParen)
catches.append(.catchAllRef(labelIndex: label))
} else {
break
}
}
// Push the try_table's label AFTER parsing catch clauses, since catch clause
// labels are resolved from the enclosing scope (the try_table's own label
// is only in scope for the body instructions).
self.labelStack.push(label)
return (bt, TryCatch(catches: catches))
}
mutating func visitBrOnNull(wat: inout Wat) throws(WatParserError) -> UInt32 {
return try labelIndex()
}
Expand Down
6 changes: 6 additions & 0 deletions Sources/WAT/Parser/WastParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ public enum WastDirective {
case assertReturn(execute: WastExecute, results: [WastExpectValue])
case assertTrap(execute: WastExecute, message: String)
case assertExhaustion(call: WastInvoke, message: String)
case assertException(execute: WastExecute)
case assertUnlinkable(module: Wat, message: String)
case register(name: String, moduleId: String?)
case invoke(WastInvoke)
Expand Down Expand Up @@ -279,6 +280,11 @@ public enum WastDirective {
let message = try wastParser.parser.expectString()
try wastParser.parser.expect(.rightParen)
return .assertExhaustion(call: call, message: message)
case "assert_exception":
try wastParser.parser.consume()
let execute = try wastParser.parens { wastParser throws(WatParserError) in try WastExecute.parse(wastParser: &wastParser) }
try wastParser.parser.expect(.rightParen)
return .assertException(execute: execute)
case "assert_unlinkable":
try wastParser.parser.consume()
let features = wastParser.features
Expand Down
Loading