Skip to content

Commit 1104c28

Browse files
committed
Function Reference: Add explicit nonNull references & call_ref
instruction
1 parent df153b0 commit 1104c28

19 files changed

+165
-83
lines changed

README.md

Lines changed: 2 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,4 +384,8 @@ extension BinaryInstructionEncoder {
384384
try encodeInstruction([0xFC, 0x10])
385385
try encodeImmediates(table: table)
386386
}
387+
mutating func visitCallRef(functionIndex: UInt32) throws {
388+
try encodeInstruction([0x14])
389+
try encodeImmediates(functionIndex: functionIndex)
390+
}
387391
}

Sources/WAT/Encoder.swift

Lines changed: 2 additions & 0 deletions
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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,9 @@ func parseTextInstruction<V: InstructionVisitor>(keyword: String, expressionPars
326326
case "i64.trunc_sat_f32_u": return { return try $0.visitConversion(.i64TruncSatF32U) }
327327
case "i64.trunc_sat_f64_s": return { return try $0.visitConversion(.i64TruncSatF64S) }
328328
case "i64.trunc_sat_f64_u": return { return try $0.visitConversion(.i64TruncSatF64U) }
329+
case "call_ref":
330+
let (functionIndex) = try expressionParser.visitCallRef(wat: &wat)
331+
return { return try $0.visitCallRef(functionIndex: functionIndex) }
329332
default: return nil
330333
}
331334
}

Sources/WAT/Parser/ExpressionParser.swift

Lines changed: 7 additions & 1 deletion
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 33 additions & 3 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ protocol BinaryInstructionDecoder {
8282
@inlinable mutating func visitTableGrow() throws -> UInt32
8383
/// Decode `table.size` immediates
8484
@inlinable mutating func visitTableSize() throws -> UInt32
85+
/// Decode `call_ref` immediates
86+
@inlinable mutating func visitCallRef() throws -> UInt32
8587
}
8688
@inlinable
8789
func parseBinaryInstruction<V: InstructionVisitor, D: BinaryInstructionDecoder>(visitor: inout V, decoder: inout D) throws -> Bool {
@@ -122,6 +124,9 @@ func parseBinaryInstruction<V: InstructionVisitor, D: BinaryInstructionDecoder>(
122124
case 0x11:
123125
let (typeIndex, tableIndex) = try decoder.visitCallIndirect()
124126
try visitor.visitCallIndirect(typeIndex: typeIndex, tableIndex: tableIndex)
127+
case 0x14:
128+
let (functionIndex) = try decoder.visitCallRef()
129+
try visitor.visitCallRef(functionIndex: functionIndex)
125130
case 0x1A:
126131
try visitor.visitDrop()
127132
case 0x1B:

Sources/WasmParser/InstructionVisitor.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ public enum Instruction: Equatable {
224224
case `tableSet`(table: UInt32)
225225
case `tableGrow`(table: UInt32)
226226
case `tableSize`(table: UInt32)
227+
case `callRef`(functionIndex: UInt32)
227228
}
228229

229230
/// A visitor that visits all instructions by a single visit method.
@@ -283,6 +284,7 @@ extension AnyInstructionVisitor {
283284
public mutating func visitTableSet(table: UInt32) throws { return try self.visit(.tableSet(table: table)) }
284285
public mutating func visitTableGrow(table: UInt32) throws { return try self.visit(.tableGrow(table: table)) }
285286
public mutating func visitTableSize(table: UInt32) throws { return try self.visit(.tableSize(table: table)) }
287+
public mutating func visitCallRef(functionIndex: UInt32) throws { return try self.visit(.callRef(functionIndex: functionIndex)) }
286288
}
287289

288290
/// A visitor for WebAssembly instructions.
@@ -390,6 +392,8 @@ public protocol InstructionVisitor {
390392
mutating func visitTableGrow(table: UInt32) throws
391393
/// Visiting `table.size` instruction.
392394
mutating func visitTableSize(table: UInt32) throws
395+
/// Visiting `call_ref` instruction.
396+
mutating func visitCallRef(functionIndex: UInt32) throws
393397
}
394398

395399
extension InstructionVisitor {
@@ -446,6 +450,7 @@ extension InstructionVisitor {
446450
case let .tableSet(table): return try visitTableSet(table: table)
447451
case let .tableGrow(table): return try visitTableGrow(table: table)
448452
case let .tableSize(table): return try visitTableSize(table: table)
453+
case let .callRef(functionIndex): return try visitCallRef(functionIndex: functionIndex)
449454
}
450455
}
451456
}
@@ -502,5 +507,6 @@ extension InstructionVisitor {
502507
public mutating func visitTableSet(table: UInt32) throws {}
503508
public mutating func visitTableGrow(table: UInt32) throws {}
504509
public mutating func visitTableSize(table: UInt32) throws {}
510+
public mutating func visitCallRef(functionIndex: UInt32) throws {}
505511
}
506512

Sources/WasmParser/WasmParser.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ extension Parser {
432432
case 0x7C: return .f64
433433
case 0x7B: return .f64
434434
case 0x70: return .ref(.funcRef)
435+
case 0x71: return .ref(.funcRefNonNull)
435436
case 0x6F: return .ref(.externRef)
436437
default:
437438
throw StreamError<Stream.Element>.unexpected(b, index: offset, expected: Set(0x7C...0x7F))
@@ -605,6 +606,10 @@ extension Parser: BinaryInstructionDecoder {
605606
return BrTable(labelIndices: labelIndices, defaultIndex: labelIndex)
606607
}
607608
@inlinable mutating func visitCall() throws -> UInt32 { try parseUnsigned() }
609+
@inlinable mutating func visitCallRef() throws -> UInt32 {
610+
// TODO reference types checks
611+
try parseUnsigned()
612+
}
608613

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

Sources/WasmTypes/WasmTypes.swift

Lines changed: 7 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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)