Skip to content

Commit c99a7d9

Browse files
committed
Function Reference: Add explicit nonNull references & call_ref
instruction
1 parent 3e01dc3 commit c99a7d9

19 files changed

+165
-83
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ and should work on the following platforms:
6969
| | [Non-trapping float-to-int conversions](https://github.com/WebAssembly/nontrapping-float-to-int-conversions/blob/main/proposals/nontrapping-float-to-int-conversion/Overview.md) | ✅ Implemented |
7070
| | [Memory64](https://github.com/WebAssembly/memory64/blob/main/proposals/memory64/Overview.md) | ✅ Implemented |
7171
| | [Threads and atomics](https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md) | 🚧 Parser implemented |
72+
| | [Typed Function References](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md) | 📋 Todo |
73+
| | [Garbage Collection](https://github.com/WebAssembly/gc/blob/main/proposals/gc/Overview.md) | 📋 Todo |
7274
| WASI | WASI Preview 1 | ✅ Implemented |
7375

7476

Sources/WAT/BinaryInstructionEncoder.swift

+4
Original file line numberDiff line numberDiff line change
@@ -392,4 +392,8 @@ extension BinaryInstructionEncoder {
392392
try encodeInstruction([0xFC, 0x10])
393393
try encodeImmediates(table: table)
394394
}
395+
mutating func visitCallRef(functionIndex: UInt32) throws {
396+
try encodeInstruction([0x14])
397+
try encodeImmediates(functionIndex: functionIndex)
398+
}
395399
}

Sources/WAT/Encoder.swift

+2
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,9 @@ extension ReferenceType: WasmEncodable {
129129
func encode(to encoder: inout Encoder) {
130130
switch self {
131131
case .funcRef: encoder.output.append(0x70)
132+
case .funcRefNonNull: encoder.output.append(0x71)
132133
case .externRef: encoder.output.append(0x6F)
134+
case .externRefNonNull: encoder.output.append(0x6E) // Is this correct
133135
}
134136
}
135137
}

Sources/WAT/ParseTextInstruction.swift

+3
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,9 @@ func parseTextInstruction<V: InstructionVisitor>(keyword: String, expressionPars
332332
case "i64.trunc_sat_f32_u": return { return try $0.visitConversion(.i64TruncSatF32U) }
333333
case "i64.trunc_sat_f64_s": return { return try $0.visitConversion(.i64TruncSatF64S) }
334334
case "i64.trunc_sat_f64_u": return { return try $0.visitConversion(.i64TruncSatF64U) }
335+
case "call_ref":
336+
let (functionIndex) = try expressionParser.visitCallRef(wat: &wat)
337+
return { return try $0.visitCallRef(functionIndex: functionIndex) }
335338
default: return nil
336339
}
337340
}

Sources/WAT/Parser/ExpressionParser.swift

+7-1
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,9 @@ struct ExpressionParser<Visitor: InstructionVisitor> {
362362
}
363363

364364
private mutating func refKind() throws -> ReferenceType {
365-
if try parser.takeKeyword("func") {
365+
if try parser.take(.id) {
366+
return .funcRef // not sure about this.
367+
} else if try parser.takeKeyword("func") {
366368
return .funcRef
367369
} else if try parser.takeKeyword("extern") {
368370
return .externRef
@@ -439,6 +441,10 @@ extension ExpressionParser {
439441
let use = try parser.expectIndexOrId()
440442
return UInt32(try wat.functionsMap.resolve(use: use).index)
441443
}
444+
mutating func visitCallRef(wat: inout Wat) throws -> UInt32 {
445+
let use = try parser.expectIndexOrId()
446+
return UInt32(try wat.types.resolve(use: use).index)
447+
}
442448
mutating func visitCallIndirect(wat: inout Wat) throws -> (typeIndex: UInt32, tableIndex: UInt32) {
443449
let tableIndex: UInt32
444450
if let tableId = try parser.takeIndexOrId() {

Sources/WAT/Parser/WastParser.swift

+2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ struct WastParser {
6767
let value: Reference
6868
switch type {
6969
case .externRef: value = .extern(nil)
70+
case .externRefNonNull: value = .function(nil) // non null
7071
case .funcRef: value = .function(nil)
72+
case .funcRefNonNull: value = .function(nil) // non null
7173
}
7274
addValue(.ref(value))
7375
}

Sources/WAT/Parser/WatParser.swift

+33-3
Original file line numberDiff line numberDiff line change
@@ -606,29 +606,59 @@ struct WatParser {
606606
}
607607

608608
mutating func valueType() throws -> ValueType {
609+
if try parser.peek(.leftParen) != nil {
610+
return try _referenceValueType()
611+
} else {
612+
return try _valueType()
613+
}
614+
}
615+
616+
// must consume right paren
617+
mutating func _referenceValueType() throws -> ValueType {
618+
var isNullable = false
619+
_ = try parser.takeParenBlockStart("ref")
620+
if try parser.peekKeyword() == "null" {
621+
_ = try parser.takeKeyword("null")
622+
isNullable = true
623+
}
624+
625+
if try parser.takeId() != nil {
626+
_ = try parser.take(.rightParen)
627+
return .ref(refType(keyword: "func", isNullable: isNullable)!)
628+
}
629+
630+
let keyword = try parser.expectKeyword()
631+
_ = try parser.take(.rightParen)
632+
if let refType = refType(keyword: keyword, isNullable: isNullable) { return .ref(refType) }
633+
throw WatParserError("unexpected value type \(keyword)", location: parser.lexer.location())
634+
}
635+
636+
mutating func _valueType() throws -> ValueType {
609637
let keyword = try parser.expectKeyword()
610638
switch keyword {
611639
case "i32": return .i32
612640
case "i64": return .i64
613641
case "f32": return .f32
614642
case "f64": return .f64
615643
default:
616-
if let refType = refType(keyword: keyword) { return .ref(refType) }
644+
if let refType = refType(keyword: keyword, isNullable: true) { return .ref(refType) }
617645
throw WatParserError("unexpected value type \(keyword)", location: parser.lexer.location())
618646
}
619647
}
620648

621-
mutating func refType(keyword: String) -> ReferenceType? {
649+
mutating func refType(keyword: String, isNullable: Bool) -> ReferenceType? {
622650
switch keyword {
623651
case "funcref": return .funcRef
624652
case "externref": return .externRef
653+
case "func": return isNullable ? .funcRef : .funcRefNonNull
654+
case "extern": return isNullable ? .externRef : .externRefNonNull
625655
default: return nil
626656
}
627657
}
628658

629659
mutating func refType() throws -> ReferenceType {
630660
let keyword = try parser.expectKeyword()
631-
guard let refType = refType(keyword: keyword) else {
661+
guard let refType = refType(keyword: keyword, isNullable: true) else {
632662
throw WatParserError("unexpected ref type \(keyword)", location: parser.lexer.location())
633663
}
634664
return refType

Sources/WasmKit/Execution/ConstEvaluation.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ extension ConstExpression {
6262
return try context.globalValue(globalIndex)
6363
case .refNull(let type):
6464
switch type {
65-
case .externRef: return .ref(.extern(nil))
66-
case .funcRef: return .ref(.function(nil))
65+
case .externRef, .externRefNonNull: return .ref(.extern(nil))
66+
case .funcRef, .funcRefNonNull: return .ref(.function(nil))
6767
}
6868
case .refFunc(let functionIndex):
6969
return try .ref(context.functionRef(functionIndex))

Sources/WasmKit/Execution/Instances.swift

+4
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,12 @@ struct TableEntity /* : ~Copyable */ {
277277
switch tableType.elementType {
278278
case .funcRef:
279279
emptyElement = .function(nil)
280+
case .funcRefNonNull:
281+
emptyElement = .function(nil) // shouldn't be null
280282
case .externRef:
281283
emptyElement = .extern(nil)
284+
case .externRefNonNull:
285+
emptyElement = .extern(nil) // shouldn't be null
282286
}
283287

284288
let numberOfElements = Int(tableType.limits.min)

Sources/WasmKit/Execution/Instructions/Misc.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ extension Execution {
2222
mutating func refNull(sp: Sp, immediate: Instruction.RefNullOperand) {
2323
let value: Value
2424
switch immediate.type {
25-
case .externRef:
25+
case .externRef, .externRefNonNull:
2626
value = .ref(.extern(nil))
27-
case .funcRef:
27+
case .funcRef, .funcRefNonNull:
2828
value = .ref(.function(nil))
2929
}
3030
sp[immediate.result] = UntypedValue(value)

Sources/WasmKit/Execution/UntypedValue.swift

+6
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,14 @@ struct UntypedValue: Equatable, Hashable {
117117
switch type {
118118
case .funcRef:
119119
return .function(decodeOptionalInt())
120+
case .funcRefNonNull:
121+
// can skip optional check
122+
return .function(decodeOptionalInt())
120123
case .externRef:
121124
return .extern(decodeOptionalInt())
125+
case .externRefNonNull:
126+
// can skip optional check
127+
return .extern(decodeOptionalInt())
122128
}
123129
}
124130

Sources/WasmKit/Validator.swift

+4
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,13 @@ struct ModuleValidator {
318318
extension WasmTypes.Reference {
319319
/// Checks if the reference type matches the expected type.
320320
func checkType(_ type: WasmTypes.ReferenceType) throws {
321+
322+
// Should we validate nonNull variants have associated values present?
321323
switch (self, type) {
322324
case (.function, .funcRef): return
325+
case (.function, .funcRefNonNull): return
323326
case (.extern, .externRef): return
327+
case (.extern, .externRefNonNull): return
324328
default:
325329
throw ValidationError(.expectTypeButGot(expected: "\(type)", got: "\(self)"))
326330
}

Sources/WasmParser/BinaryInstructionDecoder.swift

+5
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ protocol BinaryInstructionDecoder {
8686
@inlinable mutating func visitTableGrow() throws -> UInt32
8787
/// Decode `table.size` immediates
8888
@inlinable mutating func visitTableSize() throws -> UInt32
89+
/// Decode `call_ref` immediates
90+
@inlinable mutating func visitCallRef() throws -> UInt32
8991
}
9092
@inlinable
9193
func parseBinaryInstruction<V: InstructionVisitor, D: BinaryInstructionDecoder>(visitor: inout V, decoder: inout D) throws -> Bool {
@@ -132,6 +134,9 @@ func parseBinaryInstruction<V: InstructionVisitor, D: BinaryInstructionDecoder>(
132134
case 0x13:
133135
let (typeIndex, tableIndex) = try decoder.visitReturnCallIndirect()
134136
try visitor.visitReturnCallIndirect(typeIndex: typeIndex, tableIndex: tableIndex)
137+
case 0x14:
138+
let (functionIndex) = try decoder.visitCallRef()
139+
try visitor.visitCallRef(functionIndex: functionIndex)
135140
case 0x1A:
136141
try visitor.visitDrop()
137142
case 0x1B:

Sources/WasmParser/InstructionVisitor.swift

+6
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ public enum Instruction: Equatable {
226226
case `tableSet`(table: UInt32)
227227
case `tableGrow`(table: UInt32)
228228
case `tableSize`(table: UInt32)
229+
case `callRef`(functionIndex: UInt32)
229230
}
230231

231232
/// A visitor that visits all instructions by a single visit method.
@@ -287,6 +288,7 @@ extension AnyInstructionVisitor {
287288
public mutating func visitTableSet(table: UInt32) throws { return try self.visit(.tableSet(table: table)) }
288289
public mutating func visitTableGrow(table: UInt32) throws { return try self.visit(.tableGrow(table: table)) }
289290
public mutating func visitTableSize(table: UInt32) throws { return try self.visit(.tableSize(table: table)) }
291+
public mutating func visitCallRef(functionIndex: UInt32) throws { return try self.visit(.callRef(functionIndex: functionIndex)) }
290292
}
291293

292294
/// A visitor for WebAssembly instructions.
@@ -398,6 +400,8 @@ public protocol InstructionVisitor {
398400
mutating func visitTableGrow(table: UInt32) throws
399401
/// Visiting `table.size` instruction.
400402
mutating func visitTableSize(table: UInt32) throws
403+
/// Visiting `call_ref` instruction.
404+
mutating func visitCallRef(functionIndex: UInt32) throws
401405
}
402406

403407
extension InstructionVisitor {
@@ -456,6 +460,7 @@ extension InstructionVisitor {
456460
case let .tableSet(table): return try visitTableSet(table: table)
457461
case let .tableGrow(table): return try visitTableGrow(table: table)
458462
case let .tableSize(table): return try visitTableSize(table: table)
463+
case let .callRef(functionIndex): return try visitCallRef(functionIndex: functionIndex)
459464
}
460465
}
461466
}
@@ -514,5 +519,6 @@ extension InstructionVisitor {
514519
public mutating func visitTableSet(table: UInt32) throws {}
515520
public mutating func visitTableGrow(table: UInt32) throws {}
516521
public mutating func visitTableSize(table: UInt32) throws {}
522+
public mutating func visitCallRef(functionIndex: UInt32) throws {}
517523
}
518524

Sources/WasmParser/WasmParser.swift

+5
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ extension Parser {
444444
case 0x7C: return .f64
445445
case 0x7B: return .f64
446446
case 0x70: return .ref(.funcRef)
447+
case 0x71: return .ref(.funcRefNonNull)
447448
case 0x6F: return .ref(.externRef)
448449
default:
449450
throw StreamError<Stream.Element>.unexpected(b, index: offset, expected: Set(0x7C...0x7F))
@@ -621,6 +622,10 @@ extension Parser: BinaryInstructionDecoder {
621622
return BrTable(labelIndices: labelIndices, defaultIndex: labelIndex)
622623
}
623624
@inlinable mutating func visitCall() throws -> UInt32 { try parseUnsigned() }
625+
@inlinable mutating func visitCallRef() throws -> UInt32 {
626+
// TODO reference types checks
627+
try parseUnsigned()
628+
}
624629

625630
@inlinable mutating func visitCallIndirect() throws -> (typeIndex: UInt32, tableIndex: UInt32) {
626631
let typeIndex: TypeIndex = try parseUnsigned()

Sources/WasmTypes/WasmTypes.swift

+7
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,15 @@ public struct FunctionType: Equatable, Hashable {
1818
public enum ReferenceType: UInt8, Equatable, Hashable {
1919
/// A nullable reference type to a function.
2020
case funcRef
21+
22+
/// A non-nullable reference type to a function
23+
case funcRefNonNull
24+
2125
/// A nullable external reference type.
2226
case externRef
27+
28+
/// A non-nullable external reference type.
29+
case externRefNonNull
2330
}
2431

2532
public enum ValueType: Equatable, Hashable {

Tests/WasmKitTests/Spectest/Spectest.swift

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ public struct SpectestResult {
3030
"\(Int(Double(numerator) / Double(denominator) * 100))%"
3131
}
3232

33+
func sortedFailedCases() -> [String] {
34+
failedCases.map { URL(filePath: $0).pathComponents.suffix(2).joined(separator: "/") }.sorted()
35+
}
36+
3337
func dump() {
3438
print(
3539
"\(passed)/\(total) (\(percentage(passed, total)) passing, \(percentage(skipped, total)) skipped, \(percentage(failed, total)) failed)"

0 commit comments

Comments
 (0)