diff --git a/Sources/WAT/Encoder.swift b/Sources/WAT/Encoder.swift index d7036574..1eec7a04 100644 --- a/Sources/WAT/Encoder.swift +++ b/Sources/WAT/Encoder.swift @@ -88,13 +88,24 @@ struct Encoder { } mutating func writeExpression(lexer: inout Lexer, wat: inout Wat) throws { - var parser = ExpressionParser(lexer: lexer) + var parser = ExpressionParser(lexer: lexer, features: wat.features) var exprEncoder = ExpressionEncoder() try parser.parse(visitor: &exprEncoder, wat: &wat) try exprEncoder.visitEnd() output.append(contentsOf: exprEncoder.encoder.output) lexer = parser.parser.lexer } + + mutating func writeInstruction(lexer: inout Lexer, wat: inout Wat) throws { + var parser = ExpressionParser(lexer: lexer, features: wat.features) + var exprEncoder = ExpressionEncoder() + guard try parser.instruction(visitor: &exprEncoder, wat: &wat) else { + throw WatParserError("unexpected end of instruction", location: lexer.location()) + } + try exprEncoder.visitEnd() + output.append(contentsOf: exprEncoder.encoder.output) + lexer = parser.parser.lexer + } } protocol WasmEncodable { @@ -156,7 +167,7 @@ struct ElementExprCollector: AnyInstructionVisitor { mutating func parse(indices: WatParser.ElementDecl.Indices, wat: inout Wat) throws { switch indices { case .elementExprList(let lexer): - var parser = ExpressionParser(lexer: lexer) + var parser = ExpressionParser(lexer: lexer, features: wat.features) try parser.parseElemExprList(visitor: &self, wat: &wat) case .functionList(let lexer): try self.parseFunctionList(lexer: lexer, wat: wat) @@ -235,8 +246,10 @@ extension WAT.WatParser.ElementDecl { } if case let .active(_, offset) = self.mode { switch offset { - case .source(var lexer): + case .expression(var lexer): try encoder.writeExpression(lexer: &lexer, wat: &wat) + case .singleInstruction(var lexer): + try encoder.writeInstruction(lexer: &lexer, wat: &wat) case .synthesized(let offset): var exprEncoder = ExpressionEncoder() if isMemory64(tableIndex: Int(tableIndex ?? 0)) { @@ -557,7 +570,7 @@ func encode(module: inout Wat) throws -> [UInt8] { encoder.writeUnsignedLEB128(local.count) local.type.encode(to: &encoder) } - let funcTypeIndex = try function.parse(visitor: &exprEncoder, wat: &module) + let funcTypeIndex = try function.parse(visitor: &exprEncoder, wat: &module, features: module.features) functionSection.append(UInt32(funcTypeIndex)) // TODO? try exprEncoder.visitEnd() diff --git a/Sources/WAT/Lexer.swift b/Sources/WAT/Lexer.swift index c4432a2a..e914b47a 100644 --- a/Sources/WAT/Lexer.swift +++ b/Sources/WAT/Lexer.swift @@ -480,18 +480,25 @@ extension Lexer.Cursor { /// - Returns: The parsed string without underscores mutating func parseUnderscoredChars(continueParsing: (Unicode.Scalar) -> Bool) throws -> String { var value = String.UnicodeScalarView() - var lastChar: Unicode.Scalar? + var lastParsedChar: Unicode.Scalar? while let char = try peek() { - lastChar = char if char == "_" { + guard let lastChar = lastParsedChar else { + throw createError("Invalid hex number, leading underscore") + } + guard lastChar != "_" else { + throw createError("Invalid hex number, consecutive underscores") + } + lastParsedChar = char _ = try next() continue } guard continueParsing(char) else { break } + lastParsedChar = char value.append(char) _ = try next() } - if lastChar == "_" { + if lastParsedChar == "_" { throw createError("Invalid hex number, trailing underscore") } return String(value) diff --git a/Sources/WAT/NameMapping.swift b/Sources/WAT/NameMapping.swift index 9d20194d..ab7f019b 100644 --- a/Sources/WAT/NameMapping.swift +++ b/Sources/WAT/NameMapping.swift @@ -1,10 +1,18 @@ import WasmParser import WasmTypes +/// A name with its location in the source file +struct Name: Equatable { + /// The name of the module field declaration specified in $id form + let value: String + /// The location of the name in the source file + let location: Location +} + /// A module field declaration that may have its name protocol NamedModuleFieldDecl { /// The name of the module field declaration specified in $id form - var id: String? { get } + var id: Name? { get } } /// A module field declaration that may be imported from another module @@ -22,11 +30,14 @@ struct NameMapping { /// - Parameter newDecl: The declaration to add /// - Returns: The index of the added declaration @discardableResult - mutating func add(_ newDecl: Decl) -> Int { + mutating func add(_ newDecl: Decl) throws -> Int { let index = decls.count decls.append(newDecl) if let name = newDecl.id { - nameToIndex[name] = index + guard nameToIndex[name.value] == nil else { + throw WatParserError("Duplicate \(name.value) identifier", location: name.location) + } + nameToIndex[name.value] = index } return index } @@ -34,7 +45,7 @@ struct NameMapping { func resolveIndex(use: Parser.IndexOrId) throws -> Int { switch use { case .id(let id, _): - guard let byName = nameToIndex[id] else { + guard let byName = nameToIndex[id.value] else { throw WatParserError("Unknown \(Decl.self) \(id)", location: use.location) } return byName @@ -91,8 +102,8 @@ struct TypesMap { /// Adds a new function type to the mapping @discardableResult - mutating func add(_ decl: WatParser.FunctionTypeDecl) -> Int { - nameMapping.add(decl) + mutating func add(_ decl: WatParser.FunctionTypeDecl) throws -> Int { + try nameMapping.add(decl) // Normalize the function type signature without parameter names if let existing = indices[decl.type.signature] { return existing @@ -104,11 +115,11 @@ struct TypesMap { } /// Adds a new function type to the mapping without parameter names - private mutating func addAnonymousSignature(_ signature: FunctionType) -> Int { + private mutating func addAnonymousSignature(_ signature: FunctionType) throws -> Int { if let existing = indices[signature] { return existing } - return add( + return try add( WatParser.FunctionTypeDecl( id: nil, type: WatParser.FunctionType(signature: signature, parameterNames: []) @@ -118,22 +129,22 @@ struct TypesMap { private mutating func resolveBlockType( results: [ValueType], - resolveSignatureIndex: (inout TypesMap) -> Int + resolveSignatureIndex: (inout TypesMap) throws -> Int ) throws -> BlockType { if let result = results.first { guard results.count > 1 else { return .type(result) } - return .funcType(UInt32(resolveSignatureIndex(&self))) + return try .funcType(UInt32(resolveSignatureIndex(&self))) } return .empty } private mutating func resolveBlockType( signature: WasmTypes.FunctionType, - resolveSignatureIndex: (inout TypesMap) -> Int + resolveSignatureIndex: (inout TypesMap) throws -> Int ) throws -> BlockType { if signature.parameters.isEmpty { return try resolveBlockType(results: signature.results, resolveSignatureIndex: resolveSignatureIndex) } - return .funcType(UInt32(resolveSignatureIndex(&self))) + return try .funcType(UInt32(resolveSignatureIndex(&self))) } /// Resolves a block type from a list of result types @@ -142,7 +153,7 @@ struct TypesMap { results: results, resolveSignatureIndex: { let signature = FunctionType(parameters: [], results: results) - return $0.addAnonymousSignature(signature) + return try $0.addAnonymousSignature(signature) }) } @@ -151,15 +162,15 @@ struct TypesMap { return try resolveBlockType( signature: signature, resolveSignatureIndex: { - return $0.addAnonymousSignature(signature) + return try $0.addAnonymousSignature(signature) }) } /// Resolves a block type from a type use mutating func resolveBlockType(use: WatParser.TypeUse) throws -> BlockType { switch (use.index, use.inline) { - case let (indexOrId?, _): - let (type, index) = try resolve(use: indexOrId) + case let (indexOrId?, inline): + let (type, index) = try resolveAndCheck(use: indexOrId, inline: inline) return try resolveBlockType(signature: type.signature, resolveSignatureIndex: { _ in index }) case (nil, let inline?): return try resolveBlockType(signature: inline.signature) @@ -173,7 +184,7 @@ struct TypesMap { return try nameMapping.resolveIndex(use: indexOrId) case (nil, let inline): let inline = inline?.signature ?? WasmTypes.FunctionType(parameters: [], results: []) - return addAnonymousSignature(inline) + return try addAnonymousSignature(inline) } } @@ -183,18 +194,22 @@ struct TypesMap { return (decl.type, index) } + private func resolveAndCheck(use indexOrId: Parser.IndexOrId, inline: WatParser.FunctionType?) throws -> (type: WatParser.FunctionType, index: Int) { + let (found, index) = try resolve(use: indexOrId) + if let inline { + // If both index and inline type, then they must match + guard found.signature == inline.signature else { + throw WatParserError("Type mismatch \(found) != \(inline)", location: indexOrId.location) + } + } + return (found, Int(index)) + } + /// Resolves a function type from a type use with an optional inline type mutating func resolve(use: WatParser.TypeUse) throws -> (type: WatParser.FunctionType, index: Int) { switch (use.index, use.inline) { case let (indexOrId?, inline): - let (found, index) = try resolve(use: indexOrId) - if let inline { - // If both index and inline type, then they must match - guard found.signature == inline.signature else { - throw WatParserError("Type mismatch \(found) != \(inline)", location: indexOrId.location) - } - } - return (found, Int(index)) + return try resolveAndCheck(use: indexOrId, inline: inline) case (nil, let inline): // If no index and no inline type, then it's a function type with no parameters or results let inline = inline ?? WatParser.FunctionType(signature: WasmTypes.FunctionType(parameters: [], results: []), parameterNames: []) @@ -203,7 +218,7 @@ struct TypesMap { return (inline, index) } // Add inline type to the index space if it doesn't already exist - let index = add(WatParser.FunctionTypeDecl(id: nil, type: inline)) + let index = try add(WatParser.FunctionTypeDecl(id: nil, type: inline)) return (inline, index) } } diff --git a/Sources/WAT/Parser.swift b/Sources/WAT/Parser.swift index 7e721624..a016413d 100644 --- a/Sources/WAT/Parser.swift +++ b/Sources/WAT/Parser.swift @@ -80,21 +80,23 @@ internal struct Parser { } try consume() let value: UnsignedType + let makeError = { [lexer] in + WatParserError("invalid literal \(token.text(from: lexer))", location: token.location(in: lexer)) + } switch pattern { case .hexPattern(let pattern): - guard let index = UnsignedType(pattern, radix: 16) else { - throw WatParserError("invalid index \(pattern)", location: token.location(in: lexer)) - } + guard let index = UnsignedType(pattern, radix: 16) else { throw makeError() } value = index case .decimalPattern(let pattern): - guard let index = UnsignedType(pattern) else { - throw WatParserError("invalid index \(pattern)", location: token.location(in: lexer)) - } + guard let index = UnsignedType(pattern) else { throw makeError() } value = index } switch sign { case .plus, nil: return fromBitPattern(value) - case .minus: return fromBitPattern(~value &+ 1) + case .minus: + let casted = fromBitPattern(~value &+ 1) + guard casted <= 0 else { throw makeError() } + return casted } } @@ -152,7 +154,14 @@ internal struct Parser { return text } mutating func expectString() throws -> String { - String(decoding: try expectStringBytes(), as: UTF8.self) + // TODO: Use SE-0405 once we can upgrade minimum-supported Swift version to 6.0 + let bytes = try expectStringBytes() + return try bytes.withUnsafeBufferPointer { + guard let value = String._tryFromUTF8($0) else { + throw WatParserError("invalid UTF-8 string", location: lexer.location()) + } + return value + } } mutating func expectStringList() throws -> [UInt8] { @@ -180,7 +189,7 @@ internal struct Parser { } mutating func expectFloatingPoint( - _: F.Type, toBitPattern: (F) -> BitPattern, + _: F.Type, toBitPattern: (F) -> BitPattern, isNaN: (BitPattern) -> Bool, buildBitPattern: ( _ sign: FloatingPointSign, _ exponentBitPattern: UInt, @@ -193,44 +202,43 @@ internal struct Parser { return 1 &<< UInt(F.exponentBitCount) - 1 } + let makeError = { [lexer] in + WatParserError("invalid float literal \(token.text(from: lexer))", location: token.location(in: lexer)) + } + let parse = { (pattern: String) throws -> F in + // Swift's Float{32,64} initializer returns +/- infinity for too large/too small values. + // We know that the given pattern will not be expected to be parsed as infinity, + // so we can check if the parsing succeeded by checking if the returned value is infinite. + guard let value = F(pattern), !value.isInfinite else { throw makeError() } + return value + } switch token.kind { case let .float(sign, pattern): let float: F switch pattern { case .decimalPattern(let pattern): - guard let value = F(pattern) else { - throw WatParserError("invalid float \(pattern)", location: token.location(in: lexer)) - } - float = value + float = try parse(pattern) case .hexPattern(let pattern): - guard let value = F("0x" + pattern) else { - throw WatParserError("invalid float \(pattern)", location: token.location(in: lexer)) - } - float = value + float = try parse("0x" + pattern) case .inf: float = .infinity case .nan(hexPattern: nil): float = .nan case .nan(let hexPattern?): - guard let bitPattern = BitPattern(hexPattern, radix: 16) else { - throw WatParserError("invalid float \(hexPattern)", location: token.location(in: lexer)) - } - return buildBitPattern(sign ?? .plus, infinityExponent, UInt(bitPattern)) + guard let significandBitPattern = BitPattern(hexPattern, radix: 16) else { throw makeError() } + let bitPattern = buildBitPattern(sign ?? .plus, infinityExponent, UInt(significandBitPattern)) + // Ensure that the given bit pattern is a NaN. + guard isNaN(bitPattern) else { throw makeError() } + return bitPattern } return toBitPattern(sign == .minus ? -float : float) case let .integer(sign, pattern): let float: F switch pattern { case .hexPattern(let pattern): - guard let value = F("0x" + pattern) else { - throw WatParserError("invalid float \(pattern)", location: token.location(in: lexer)) - } - float = value + float = try parse("0x" + pattern) case .decimalPattern(let pattern): - guard let value = F(pattern) else { - throw WatParserError("invalid float \(pattern)", location: token.location(in: lexer)) - } - float = value + float = try parse(pattern) } return toBitPattern(sign == .minus ? -float : float) default: @@ -241,6 +249,7 @@ internal struct Parser { mutating func expectFloat32() throws -> IEEE754.Float32 { let bitPattern = try expectFloatingPoint( Float32.self, toBitPattern: \.bitPattern, + isNaN: { Float32(bitPattern: $0).isNaN }, buildBitPattern: { UInt32( ($0 == .minus ? 1 : 0) << (Float32.exponentBitCount + Float32.significandBitCount) @@ -254,6 +263,7 @@ internal struct Parser { mutating func expectFloat64() throws -> IEEE754.Float64 { let bitPattern = try expectFloatingPoint( Float64.self, toBitPattern: \.bitPattern, + isNaN: { Float64(bitPattern: $0).isNaN }, buildBitPattern: { UInt64( ($0 == .minus ? 1 : 0) << (Float64.exponentBitCount + Float64.significandBitCount) @@ -274,7 +284,7 @@ internal struct Parser { enum IndexOrId { case index(UInt32, Location) - case id(String, Location) + case id(Name, Location) var location: Location { switch self { case .index(_, let location), .id(_, let location): @@ -303,10 +313,10 @@ internal struct Parser { return token } - mutating func takeId() throws -> String? { + mutating func takeId() throws -> Name? { guard let token = try peek(.id) else { return nil } try consume() - return token.text(from: lexer) + return Name(value: token.text(from: lexer), location: token.location(in: lexer)) } mutating func skipParenBlock() throws { diff --git a/Sources/WAT/Parser/ExpressionParser.swift b/Sources/WAT/Parser/ExpressionParser.swift index 6150d882..e886738c 100644 --- a/Sources/WAT/Parser/ExpressionParser.swift +++ b/Sources/WAT/Parser/ExpressionParser.swift @@ -18,12 +18,12 @@ struct ExpressionParser { case .index(let index, _): return Int(index) case .id(let name, _): - return self[name] + return self[name.value] } } - mutating func push(_ name: String?) { - stack.append(name) + mutating func push(_ name: Name?) { + stack.append(name?.value) } mutating func pop() { @@ -36,29 +36,33 @@ struct ExpressionParser { } var parser: Parser let locals: LocalsMap + let features: WasmFeatureSet private var labelStack = LabelStack() init( type: WatParser.FunctionType, locals: [WatParser.LocalDecl], - lexer: Lexer + lexer: Lexer, + features: WasmFeatureSet ) throws { self.parser = Parser(lexer) self.locals = try Self.computeLocals(type: type, locals: locals) + self.features = features } - init(lexer: Lexer) { + init(lexer: Lexer, features: WasmFeatureSet) { self.parser = Parser(lexer) self.locals = LocalsMap() + self.features = features } static func computeLocals(type: WatParser.FunctionType, locals: [WatParser.LocalDecl]) throws -> LocalsMap { var localsMap = LocalsMap() for (name, type) in zip(type.parameterNames, type.signature.parameters) { - localsMap.add(WatParser.LocalDecl(id: name, type: type)) + try localsMap.add(WatParser.LocalDecl(id: name, type: type)) } for local in locals { - localsMap.add(local) + try localsMap.add(local) } return localsMap } @@ -82,22 +86,15 @@ struct ExpressionParser { guard let lastLabel = maybeLastLabel else { throw WatParserError("unexpected label \(name)", location: location) } - guard lastLabel == name else { + guard lastLabel == name.value else { throw WatParserError("expected label \(lastLabel) but found \(name)", location: location) } } - @discardableResult - mutating func parse(visitor: inout Visitor, wat: inout Wat) throws -> Int { - var numberOfInstructions = 0 - while true { - guard try instruction(visitor: &visitor, wat: &wat) else { - numberOfInstructions += 1 - break - } + mutating func parse(visitor: inout Visitor, wat: inout Wat) throws { + while try instruction(visitor: &visitor, wat: &wat) { // Parse more instructions } - return numberOfInstructions } mutating func parseElemExprList(visitor: inout Visitor, wat: inout Wat) throws { @@ -115,7 +112,7 @@ struct ExpressionParser { mutating func parseWastConstInstruction( visitor: inout Visitor ) throws -> Bool where Visitor: WastConstInstructionVisitor { - var wat = Wat.empty() + var wat = Wat.empty(features: features) // WAST allows extra const value instruction if try parser.takeParenBlockStart("ref.extern") { _ = try visitor.visitRefExtern(value: parser.expectUnsignedInt()) @@ -130,7 +127,7 @@ struct ExpressionParser { } mutating func parseConstInstruction(visitor: inout Visitor) throws -> Bool { - var wat = Wat.empty() + var wat = Wat.empty(features: features) if try foldedInstruction(visitor: &visitor, wat: &wat) { return true } @@ -166,7 +163,7 @@ struct ExpressionParser { /// Parse "(instr)" or "instr" and visit the instruction. /// - Returns: `true` if an instruction was parsed. Otherwise, `false`. - private mutating func instruction(visitor: inout Visitor, wat: inout Wat) throws -> Bool { + mutating func instruction(visitor: inout Visitor, wat: inout Wat) throws -> Bool { if try nonFoldedInstruction(visitor: &visitor, wat: &wat) { return true } @@ -202,9 +199,7 @@ struct ExpressionParser { } try parser.expect(.leftParen) let keyword = try parser.expectKeyword() - guard let visit = try parseTextInstruction(keyword: keyword, wat: &wat) else { - return false - } + let visit = try parseTextInstruction(keyword: keyword, wat: &wat) let suspense: Suspense switch keyword { case "if": @@ -261,7 +256,7 @@ struct ExpressionParser { } /// Parse a single instruction without consuming the surrounding parentheses and instruction keyword. - private mutating func parseTextInstruction(keyword: String, wat: inout Wat) throws -> ((inout Visitor) throws -> Visitor.Output)? { + private mutating func parseTextInstruction(keyword: String, wat: inout Wat) throws -> ((inout Visitor) throws -> Visitor.Output) { switch keyword { case "select": // Special handling for "select", which have two variants 1. with type, 2. without type @@ -289,7 +284,10 @@ struct ExpressionParser { } default: // Other instructions are parsed by auto-generated code. - return try WAT.parseTextInstruction(keyword: keyword, expressionParser: &self, wat: &wat) + guard let visit = try WAT.parseTextInstruction(keyword: keyword, expressionParser: &self, wat: &wat) else { + throw WatParserError("unknown instruction \(keyword)", location: parser.lexer.location()) + } + return visit } } @@ -298,12 +296,8 @@ struct ExpressionParser { guard let keyword = try parser.peekKeyword() else { return false } - let originalParser = parser try parser.consume() - guard let visit = try parseTextInstruction(keyword: keyword, wat: &wat) else { - parser = originalParser - return false - } + let visit = try parseTextInstruction(keyword: keyword, wat: &wat) _ = try visit(&visitor) return true } @@ -348,7 +342,7 @@ struct ExpressionParser { if !results.isEmpty { return try wat.types.resolveBlockType(results: results) } - let typeUse = try withWatParser { try $0.typeUse() } + let typeUse = try withWatParser { try $0.typeUse(mayHaveName: false) } return try wat.types.resolveBlockType(use: typeUse) } @@ -383,6 +377,10 @@ struct ExpressionParser { try parser.consume() var subParser = Parser(String(maybeOffset.dropFirst(offsetPrefix.count))) offset = try subParser.expectUnsignedInt(UInt64.self) + + if !features.contains(.memory64), offset > UInt32.max { + throw WatParserError("memory offset must be less than or equal to \(UInt32.max)", location: subParser.lexer.location()) + } } var align: UInt32 = defaultAlign let alignPrefix = "align=" @@ -390,6 +388,10 @@ struct ExpressionParser { try parser.consume() var subParser = Parser(String(maybeAlign.dropFirst(alignPrefix.count))) align = try subParser.expectUnsignedInt(UInt32.self) + + if align == 0 || align & (align - 1) != 0 { + throw WatParserError("alignment must be a power of 2", location: subParser.lexer.location()) + } } return MemArg(offset: offset, align: align) } @@ -443,7 +445,7 @@ extension ExpressionParser { } else { tableIndex = 0 } - let typeUse = try withWatParser { try $0.typeUse() } + let typeUse = try withWatParser { try $0.typeUse(mayHaveName: false) } let (_, typeIndex) = try wat.types.resolve(use: typeUse) return (UInt32(typeIndex), tableIndex) } diff --git a/Sources/WAT/Parser/WastParser.swift b/Sources/WAT/Parser/WastParser.swift index e21483f6..7cd6f39a 100644 --- a/Sources/WAT/Parser/WastParser.swift +++ b/Sources/WAT/Parser/WastParser.swift @@ -9,9 +9,11 @@ protocol WastConstInstructionVisitor: InstructionVisitor { /// You can find its grammar definition in the [WebAssembly spec repository](https://github.com/WebAssembly/spec/blob/wg-1.0/interpreter/README.md#scripts) struct WastParser { var parser: Parser + let features: WasmFeatureSet - init(_ input: String) { + init(_ input: String, features: WasmFeatureSet) { self.parser = Parser(input) + self.features = features } mutating func nextDirective() throws -> WastDirective? { @@ -24,7 +26,7 @@ struct WastParser { let location = originalParser.lexer.location() return .module( ModuleDirective( - source: .text(try parseWAT(&originalParser)), id: nil, location: location + source: .text(try parseWAT(&originalParser, features: features)), id: nil, location: location )) } throw WatParserError("unexpected wast directive token", location: parser.lexer.location()) @@ -78,7 +80,7 @@ struct WastParser { mutating func constExpression() throws -> [Value] { var values: [Value] = [] var collector = ConstExpressionCollector(addValue: { values.append($0) }) - var exprParser = ExpressionParser(lexer: parser.lexer) + var exprParser = ExpressionParser(lexer: parser.lexer, features: features) while try exprParser.parseWastConstInstruction(visitor: &collector) {} parser = exprParser.parser return values @@ -87,7 +89,7 @@ struct WastParser { mutating func expectationValues() throws -> [WastExpectValue] { var values: [WastExpectValue] = [] var collector = ConstExpressionCollector(addValue: { values.append(.value($0)) }) - var exprParser = ExpressionParser(lexer: parser.lexer) + var exprParser = ExpressionParser(lexer: parser.lexer, features: features) while true { if let expectValue = try exprParser.parseWastExpectValue() { values.append(expectValue) @@ -115,13 +117,13 @@ public enum WastExecute { execute = .invoke(try WastInvoke.parse(wastParser: &wastParser)) case "module": try wastParser.parser.consume() - execute = .wat(try parseWAT(&wastParser.parser)) + execute = .wat(try parseWAT(&wastParser.parser, features: wastParser.features)) try wastParser.parser.skipParenBlock() case "get": try wastParser.parser.consume() let module = try wastParser.parser.takeId() let globalName = try wastParser.parser.expectString() - execute = .get(module: module, globalName: globalName) + execute = .get(module: module?.value, globalName: globalName) try wastParser.parser.expect(.rightParen) case let keyword?: throw WatParserError( @@ -146,7 +148,7 @@ public struct WastInvoke { let name = try wastParser.parser.expectString() let args = try wastParser.constExpression() try wastParser.parser.expect(.rightParen) - let invoke = WastInvoke(module: module, name: name, args: args) + let invoke = WastInvoke(module: module?.value, name: name, args: args) return invoke } } @@ -224,9 +226,10 @@ public enum WastDirective { return .assertExhaustion(call: call, message: message) case "assert_unlinkable": try wastParser.parser.consume() + let features = wastParser.features let module = try wastParser.parens { try $0.parser.expectKeyword("module") - let wat = try parseWAT(&$0.parser) + let wat = try parseWAT(&$0.parser, features: features) try $0.parser.skipParenBlock() return wat } @@ -238,7 +241,7 @@ public enum WastDirective { let name = try wastParser.parser.expectString() let module = try wastParser.parser.takeId() try wastParser.parser.expect(.rightParen) - return .register(name: name, moduleId: module) + return .register(name: name, moduleId: module?.value) case "invoke": let invoke = try WastInvoke.parse(wastParser: &wastParser) return .invoke(invoke) @@ -265,9 +268,9 @@ public struct ModuleDirective { static func parse(wastParser: inout WastParser) throws -> ModuleDirective { let location = wastParser.parser.lexer.location() try wastParser.parser.expectKeyword("module") - let idToken = try wastParser.parser.takeId() + let id = try wastParser.parser.takeId() let source = try ModuleSource.parse(wastParser: &wastParser) - return ModuleDirective(source: source, id: idToken, location: location) + return ModuleDirective(source: source, id: id?.value, location: location) } } @@ -293,7 +296,7 @@ public enum ModuleSource { } } - let watModule = try parseWAT(&wastParser.parser) + let watModule = try parseWAT(&wastParser.parser, features: wastParser.features) try wastParser.parser.skipParenBlock() return .text(watModule) } diff --git a/Sources/WAT/Parser/WatParser.swift b/Sources/WAT/Parser/WatParser.swift index 31504ed1..530494a4 100644 --- a/Sources/WAT/Parser/WatParser.swift +++ b/Sources/WAT/Parser/WatParser.swift @@ -43,7 +43,7 @@ struct WatParser { struct FunctionType { let signature: WasmTypes.FunctionType /// Names of the parameters. The number of names must match the number of parameters in `type`. - let parameterNames: [String?] + let parameterNames: [Name?] } /// Represents a type use in a function signature. @@ -59,12 +59,12 @@ struct WatParser { } struct LocalDecl: NamedModuleFieldDecl { - var id: String? + var id: Name? var type: ValueType } struct FunctionDecl: NamedModuleFieldDecl, ImportableModuleFieldDecl { - var id: String? + var id: Name? var exports: [String] var typeUse: TypeUse var kind: FunctionKind @@ -80,24 +80,28 @@ struct WatParser { /// This method may modify TypesMap of the given WATModule /// /// - Returns: Type index of this function - func parse(visitor: inout V, wat: inout Wat) throws -> Int { + func parse(visitor: inout V, wat: inout Wat, features: WasmFeatureSet) throws -> Int { guard case let .definition(locals, body) = kind else { fatalError("Imported functions cannot be parsed") } let (type, typeIndex) = try wat.types.resolve(use: typeUse) - var parser = try ExpressionParser(type: type, locals: locals, lexer: body) + var parser = try ExpressionParser(type: type, locals: locals, lexer: body, features: features) try parser.parse(visitor: &visitor, wat: &wat) + // Check if the parser has reached the end of the function body + guard try parser.parser.isEndOfParen() else { + throw WatParserError("unexpected token", location: parser.parser.lexer.location()) + } return typeIndex } } struct FunctionTypeDecl: NamedModuleFieldDecl { - let id: String? + let id: Name? let type: FunctionType } struct TableDecl: NamedModuleFieldDecl, ImportableModuleFieldDecl { - var id: String? + var id: Name? var exports: [String] var type: TableType var importNames: ImportNames? @@ -106,7 +110,8 @@ struct WatParser { struct ElementDecl: NamedModuleFieldDecl { enum Offset { - case source(Lexer) + case expression(Lexer) + case singleInstruction(Lexer) case synthesized(Int) } enum Mode { @@ -121,7 +126,7 @@ struct WatParser { case elementExprList(Lexer) } - var id: String? + var id: Name? var mode: Mode var type: ReferenceType var indices: Indices @@ -134,7 +139,7 @@ struct WatParser { } struct GlobalDecl: NamedModuleFieldDecl, ImportableModuleFieldDecl { - var id: String? + var id: Name? var exports: [String] var type: GlobalType var kind: GlobalKind @@ -148,7 +153,7 @@ struct WatParser { } struct MemoryDecl: NamedModuleFieldDecl, ImportableModuleFieldDecl { - var id: String? + var id: Name? var exports: [String] var type: MemoryType var importNames: ImportNames? @@ -156,7 +161,7 @@ struct WatParser { } struct DataSegmentDecl: NamedModuleFieldDecl { - var id: String? + var id: Name? var memory: Parser.IndexOrId? enum Offset { case source(Lexer) @@ -196,7 +201,7 @@ struct WatParser { let importNames = try importNames() if try parser.takeParenBlockStart("func") { let id = try parser.takeId() - kind = .function(FunctionDecl(id: id, exports: [], typeUse: try typeUse(), kind: .imported(importNames))) + kind = .function(FunctionDecl(id: id, exports: [], typeUse: try typeUse(mayHaveName: true), kind: .imported(importNames))) } else if try parser.takeParenBlockStart("table") { let id = try parser.takeId() kind = .table(TableDecl(id: id, exports: [], type: try tableType(), importNames: importNames)) @@ -215,7 +220,7 @@ struct WatParser { let id = try parser.takeId() let exports = try inlineExports() let importNames = try inlineImport() - let typeUse = try typeUse() + let typeUse = try typeUse(mayHaveName: true) let functionKind: FunctionKind if let importNames = importNames { functionKind = .imported(importNames) @@ -350,12 +355,12 @@ struct WatParser { } else { table = try tableUse() if try parser.takeParenBlockStart("offset") { - mode = .active(table: table, offset: .source(parser.lexer)) + mode = .active(table: table, offset: .expression(parser.lexer)) try parser.skipParenBlock() } else { if try parser.peek(.leftParen) != nil { // abbreviated offset instruction - mode = .active(table: table, offset: .source(parser.lexer)) + mode = .active(table: table, offset: .singleInstruction(parser.lexer)) try parser.consume() // consume ( try parser.skipParenBlock() // skip offset expr } else { @@ -441,14 +446,14 @@ struct WatParser { return ImportNames(module: module, name: name) } - mutating func typeUse() throws -> TypeUse { + mutating func typeUse(mayHaveName: Bool) throws -> TypeUse { let location = parser.lexer.location() var index: Parser.IndexOrId? if try parser.takeParenBlockStart("type") { index = try parser.expectIndexOrId() try parser.expect(.rightParen) } - let inline = try optionalFunctionType() + let inline = try optionalFunctionType(mayHaveName: mayHaveName) return TypeUse(index: index, inline: inline, location: location) } @@ -552,14 +557,14 @@ struct WatParser { mutating func funcType() throws -> FunctionType { try parser.expect(.leftParen) try parser.expectKeyword("func") - let (params, names) = try params() + let (params, names) = try params(mayHaveName: true) let results = try results() try parser.expect(.rightParen) return FunctionType(signature: WasmTypes.FunctionType(parameters: params, results: results), parameterNames: names) } - mutating func optionalFunctionType() throws -> FunctionType? { - let (params, names) = try params() + mutating func optionalFunctionType(mayHaveName: Bool) throws -> FunctionType? { + let (params, names) = try params(mayHaveName: mayHaveName) let results = try results() if results.isEmpty, params.isEmpty { return nil @@ -567,22 +572,24 @@ struct WatParser { return FunctionType(signature: WasmTypes.FunctionType(parameters: params, results: results), parameterNames: names) } - mutating func params() throws -> ([ValueType], [String?]) { + mutating func params(mayHaveName: Bool) throws -> ([ValueType], [Name?]) { var types: [ValueType] = [] - var names: [String?] = [] + var names: [Name?] = [] while try parser.takeParenBlockStart("param") { - if let id = try parser.takeId() { - let valueType = try valueType() - types.append(valueType) - names.append(id) - try parser.expect(.rightParen) - } else { - while try !parser.take(.rightParen) { + if mayHaveName { + if let id = try parser.takeId() { let valueType = try valueType() types.append(valueType) - names.append(nil) + names.append(id) + try parser.expect(.rightParen) + continue } } + while try !parser.take(.rightParen) { + let valueType = try valueType() + types.append(valueType) + names.append(nil) + } } return (types, names) } diff --git a/Sources/WAT/WAT.swift b/Sources/WAT/WAT.swift index 70421e4e..519586c1 100644 --- a/Sources/WAT/WAT.swift +++ b/Sources/WAT/WAT.swift @@ -17,8 +17,8 @@ import WasmParser /// ) /// """) /// ``` -public func wat2wasm(_ input: String) throws -> [UInt8] { - var wat = try parseWAT(input) +public func wat2wasm(_ input: String, features: WasmFeatureSet = .default) throws -> [UInt8] { + var wat = try parseWAT(input, features: features) return try encode(module: &wat) } @@ -36,10 +36,11 @@ public struct Wat { let imports: [Import] let exports: [Export] let customSections = [CustomSection]() + let features: WasmFeatureSet let parser: Parser - static func empty() -> Wat { + static func empty(features: WasmFeatureSet) -> Wat { Wat( types: TypesMap(), functionsMap: NameMapping(), @@ -52,6 +53,7 @@ public struct Wat { start: nil, imports: [], exports: [], + features: features, parser: Parser("") ) } @@ -89,15 +91,15 @@ public struct Wat { /// /// let wasm = try wat.encode() /// ``` -public func parseWAT(_ input: String) throws -> Wat { +public func parseWAT(_ input: String, features: WasmFeatureSet = .default) throws -> Wat { var parser = Parser(input) let wat: Wat if try parser.takeParenBlockStart("module") { - wat = try parseWAT(&parser) + wat = try parseWAT(&parser, features: features) try parser.skipParenBlock() } else { // The root (module) may be omitted - wat = try parseWAT(&parser) + wat = try parseWAT(&parser, features: features) } return wat } @@ -106,8 +108,8 @@ public func parseWAT(_ input: String) throws -> Wat { public struct Wast { var parser: WastParser - init(_ input: String) { - self.parser = WastParser(input) + init(_ input: String, features: WasmFeatureSet) { + self.parser = WastParser(input, features: features) } /// Parses the next directive in the WAST script. @@ -147,11 +149,11 @@ public struct Wast { /// print("\(location): \(directive)") /// } /// ``` -public func parseWAST(_ input: String) throws -> Wast { - return Wast(input) +public func parseWAST(_ input: String, features: WasmFeatureSet = .default) throws -> Wast { + return Wast(input, features: features) } -func parseWAT(_ parser: inout Parser) throws -> Wat { +func parseWAT(_ parser: inout Parser, features: WasmFeatureSet) throws -> Wat { // This parser is 2-pass: first it collects all module items and creates a mapping of names to indices. let initialParser = parser @@ -168,6 +170,7 @@ func parseWAT(_ parser: inout Parser) throws -> Wat { var exportDecls: [WatParser.ExportDecl] = [] + var hasNonImport = false func visitDecl(decl: WatParser.ModuleField) throws { let location = decl.location @@ -186,11 +189,24 @@ func parseWAT(_ parser: inout Parser) throws -> Wat { } } + // Verify that imports precede all non-import module fields + func checkImportOrder(_ importNames: WatParser.ImportNames?) throws { + if importNames != nil { + if hasNonImport { + throw WatParserError("Imports must precede all non-import module fields", location: location) + } + } else { + hasNonImport = true + } + } + + switch decl.kind { case let .type(decl): - typesMap.add(decl) + try typesMap.add(decl) case let .function(decl): - let index = functionsMap.add(decl) + try checkImportOrder(decl.importNames) + let index = try functionsMap.add(decl) addExports(decl.exports, index: index, kind: .function) switch decl.kind { case .definition: break @@ -201,31 +217,34 @@ func parseWAT(_ parser: inout Parser) throws -> Wat { } } case let .table(decl): - let index = tablesMap.add(decl) + try checkImportOrder(decl.importNames) + let index = try tablesMap.add(decl) addExports(decl.exports, index: index, kind: .table) if var inlineElement = decl.inlineElement { inlineElement.mode = .active( table: .index(UInt32(index), location), offset: .synthesized(0) ) - elementSegmentsMap.add(inlineElement) + try elementSegmentsMap.add(inlineElement) } if let importNames = decl.importNames { addImport(importNames) { .table(decl.type) } } case let .memory(decl): - let index = memoriesMap.add(decl) + try checkImportOrder(decl.importNames) + let index = try memoriesMap.add(decl) if var inlineData = decl.inlineData { // Associate the memory with the inline data inlineData.memory = .index(UInt32(index), location) inlineData.offset = .synthesized(0) - dataSegmentsMap.add(inlineData) + try dataSegmentsMap.add(inlineData) } addExports(decl.exports, index: index, kind: .memory) if let importNames = decl.importNames { addImport(importNames) { .memory(decl.type) } } case let .global(decl): - let index = globalsMap.add(decl) + try checkImportOrder(decl.importNames) + let index = try globalsMap.add(decl) addExports(decl.exports, index: index, kind: .global) switch decl.kind { case .definition: break @@ -233,12 +252,15 @@ func parseWAT(_ parser: inout Parser) throws -> Wat { addImport(importNames) { .global(decl.type) } } case let .element(decl): - elementSegmentsMap.add(decl) + try elementSegmentsMap.add(decl) case let .export(decl): exportDecls.append(decl) case let .data(decl): - dataSegmentsMap.add(decl) + try dataSegmentsMap.add(decl) case let .start(startIndex): + guard start == nil else { + throw WatParserError("Multiple start sections", location: location) + } start = startIndex } } @@ -285,6 +307,7 @@ func parseWAT(_ parser: inout Parser) throws -> Wat { start: startIndex, imports: imports, exports: exports, + features: features, parser: parser ) } diff --git a/Sources/WasmKit/Engine.swift b/Sources/WasmKit/Engine.swift index bc77fe94..50ef7176 100644 --- a/Sources/WasmKit/Engine.swift +++ b/Sources/WasmKit/Engine.swift @@ -53,12 +53,28 @@ public struct EngineConfiguration { /// The threading model to use for the virtual machine interpreter. public var threadingModel: ThreadingModel + /// The compilation mode of WebAssembly modules to the internal virtual + /// machine instruction sequence. + public enum CompilationMode { + /// Eager compilation, where the module is compiled to the internal + /// instruction sequence immediately after instantiation. + case eager + + /// Lazy compilation, where the module is compiled to the internal + /// instruction sequence only when the first function is called. + case lazy + } + + /// The compilation mode to use for WebAssembly modules. + public var compilationMode: CompilationMode + /// Initializes a new instance of `EngineConfiguration`. /// - Parameter threadingModel: The threading model to use for the virtual /// machine interpreter. If `nil`, the default threading model for the /// current platform will be used. - public init(threadingModel: ThreadingModel? = nil) { + public init(threadingModel: ThreadingModel? = nil, compilationMode: CompilationMode = .lazy) { self.threadingModel = threadingModel ?? .defaultForCurrentPlatform + self.compilationMode = compilationMode } } diff --git a/Sources/WasmKit/Execution/Execution.swift b/Sources/WasmKit/Execution/Execution.swift index cec7c752..6aaca3a6 100644 --- a/Sources/WasmKit/Execution/Execution.swift +++ b/Sources/WasmKit/Execution/Execution.swift @@ -435,9 +435,7 @@ extension Execution { ) throws -> (Pc, Sp) { if function.isWasm { let function = function.wasm - let iseq = try function.withValue { - try $0.ensureCompiled(context: &self) - } + let iseq = try function.ensureCompiled(store: store) let newSp = try pushFrame( iseq: iseq, diff --git a/Sources/WasmKit/Execution/Function.swift b/Sources/WasmKit/Execution/Function.swift index de5c7f1a..7dd1d5b6 100644 --- a/Sources/WasmKit/Execution/Function.swift +++ b/Sources/WasmKit/Execution/Function.swift @@ -181,19 +181,6 @@ extension InternalFunction { } } - @inline(never) - func ensureCompiled(store: StoreRef) throws { - let entity = self.wasm - switch entity.code { - case .uncompiled(let code): - try entity.withValue { - let iseq = try $0.compile(store: store, code: code) - $0.code = .compiled(iseq) - } - case .compiled: break - } - } - func assumeCompiled() -> ( InstructionSequence, locals: Int, @@ -223,10 +210,6 @@ struct WasmFunctionEntity { self.index = index } - mutating func ensureCompiled(context: inout Execution) throws -> InstructionSequence { - try ensureCompiled(store: context.store) - } - mutating func ensureCompiled(store: StoreRef) throws -> InstructionSequence { switch code { case .uncompiled(let code): @@ -260,6 +243,22 @@ struct WasmFunctionEntity { } } +extension EntityHandle { + @inline(never) + @discardableResult + func ensureCompiled(store: StoreRef) throws -> InstructionSequence { + switch self.code { + case .uncompiled(let code): + return try self.withValue { + let iseq = try $0.compile(store: store, code: code) + $0.code = .compiled(iseq) + return iseq + } + case .compiled(let iseq): return iseq + } + } +} + typealias InternalUncompiledCode = EntityHandle /// A compiled instruction sequence. diff --git a/Sources/WasmKit/Execution/Instances.swift b/Sources/WasmKit/Execution/Instances.swift index f12aad6a..9f0f6e4f 100644 --- a/Sources/WasmKit/Execution/Instances.swift +++ b/Sources/WasmKit/Execution/Instances.swift @@ -93,6 +93,14 @@ struct InstanceEntity /* : ~Copyable */ { hasDataCount: false ) } + + internal func compileAllFunctions(store: Store) throws { + let store = StoreRef(store) + for function in functions { + guard function.isWasm else { continue } + try function.wasm.ensureCompiled(store: store) + } + } } typealias InternalInstance = EntityHandle @@ -209,7 +217,7 @@ public struct Instance { guard case .uncompiled(let code) = function.wasm.code else { fatalError("Already compiled!?") } - try function.ensureCompiled(store: StoreRef(store)) + try function.wasm.ensureCompiled(store: StoreRef(store)) let (iseq, locals, _) = function.assumeCompiled() // Print slot space information @@ -506,8 +514,16 @@ public struct Global: Equatable { /// Initializes a new global instance with the given type and initial value. /// The returned global instance may be used to instantiate a new /// WebAssembly module. + @available(*, deprecated, renamed: "init(store:type:value:)") public init(globalType: GlobalType, initialValue: Value, store: Store) { - let handle = store.allocator.allocate(globalType: globalType, initialValue: initialValue) + self.init(store: store, type: globalType, value: initialValue) + } + + /// Initializes a new global instance with the given type and initial value. + /// The returned global instance may be used to instantiate a new + /// WebAssembly module. + public init(store: Store, type: GlobalType, value: Value) { + let handle = store.allocator.allocate(globalType: type, initialValue: value) self.init(handle: handle, allocator: store.allocator) } } diff --git a/Sources/WasmKit/Execution/Instructions/Control.swift b/Sources/WasmKit/Execution/Instructions/Control.swift index d7929f72..a3566222 100644 --- a/Sources/WasmKit/Execution/Instructions/Control.swift +++ b/Sources/WasmKit/Execution/Instructions/Control.swift @@ -102,7 +102,7 @@ extension Execution { // NOTE: `CompilingCallOperand` consumes 2 slots, discriminator is at -3 let headSlotPc = pc.advanced(by: -3) let callee = immediate.callee - try callee.ensureCompiled(store: store) + try callee.wasm.ensureCompiled(store: store) let replaced = Instruction.internalCall(immediate) headSlotPc.pointee = replaced.headSlot(threadingModel: store.value.engine.configuration.threadingModel) try _internalCall(sp: &sp, pc: &pc, callee: callee, internalCallOperand: immediate) diff --git a/Sources/WasmKit/Module.swift b/Sources/WasmKit/Module.swift index af789fd9..b466864c 100644 --- a/Sources/WasmKit/Module.swift +++ b/Sources/WasmKit/Module.swift @@ -199,6 +199,13 @@ public struct Module { _ = try startFunction.invoke([], store: store) } + // Compile all functions eagerly if the engine is in eager compilation mode + if store.engine.configuration.compilationMode == .eager { + try instance.withValue { + try $0.compileAllFunctions(store: store) + } + } + return instance } diff --git a/Tests/WATTests/EncoderTests.swift b/Tests/WATTests/EncoderTests.swift index 09dd2ba6..db772d5f 100644 --- a/Tests/WATTests/EncoderTests.swift +++ b/Tests/WATTests/EncoderTests.swift @@ -12,8 +12,10 @@ class EncoderTests: XCTestCase { } func checkWabtCompatibility( - wast: URL, json: URL, stats: inout CompatibilityTestStats + wast: URL, json: URL, stats parentStats: inout CompatibilityTestStats ) throws { + var stats = parentStats + defer { parentStats = stats } func recordFail() { stats.failed.insert(wast.lastPathComponent) } @@ -25,11 +27,32 @@ class EncoderTests: XCTestCase { } print("Checking\n wast: \(wast.path)\n json: \(json.path)") - var parser = WastParser(try String(contentsOf: wast)) + var parser = WastParser(try String(contentsOf: wast), features: Spectest.deriveFeatureSet(wast: wast)) var watModules: [ModuleDirective] = [] + while let directive = try parser.nextDirective() { - if case let .module(moduleDirective) = directive { + switch directive { + case .module(let moduleDirective): watModules.append(moduleDirective) + case .assertMalformed(let module, let message): + let diagnostic = { + let (line, column) = module.location.computeLineAndColumn() + return "\(wast.path):\(line):\(column) should be malformed: \(message)" + } + switch module.source { + case .text(var wat): + XCTAssertThrowsError(try { + _ = try wat.encode() + recordFail() + }(), diagnostic()) + case .quote(let bytes): + XCTAssertThrowsError(try { + _ = try wat2wasm(String(decoding: bytes, as: UTF8.self)) + recordFail() + }(), diagnostic()) + case .binary: break + } + default: break } } guard FileManager.default.fileExists(atPath: json.path) else { diff --git a/Tests/WATTests/ParserTests.swift b/Tests/WATTests/ParserTests.swift index e1cc0ff0..c4d4e660 100644 --- a/Tests/WATTests/ParserTests.swift +++ b/Tests/WATTests/ParserTests.swift @@ -5,8 +5,8 @@ import XCTest @testable import WAT class ParserTests: XCTestCase { - func parseWast(_ source: String) throws -> [WastDirective] { - var parser = WastParser(source) + func parseWast(_ source: String, features: WasmFeatureSet = .default) throws -> [WastDirective] { + var parser = WastParser(source, features: features) var directives: [WastDirective] = [] while let directive = try parser.nextDirective() { directives.append(directive) @@ -66,7 +66,7 @@ class ParserTests: XCTestCase { (unknown expr) ) ) - """#) + """#, features: .default) while let directive = try parser.nextDirective() { switch directive { @@ -140,7 +140,7 @@ class ParserTests: XCTestCase { let (evenType, _) = try wat.types.resolve(use: even.typeUse) XCTAssertEqual(evenType.signature.parameters, [.i32]) XCTAssertEqual(evenType.signature.results.first, .i32) - XCTAssertEqual(evenType.parameterNames, ["$n"]) + XCTAssertEqual(evenType.parameterNames.map(\.?.value), ["$n"]) } func testFuncIdBinding() throws { @@ -185,7 +185,7 @@ class ParserTests: XCTestCase { totalCount += 1 let source = try String(contentsOf: filePath) do { - _ = try parseWast(source) + _ = try parseWast(source, features: Spectest.deriveFeatureSet(wast: filePath)) } catch { failureCount += 1 XCTFail("Failed to parse \(filePath.path):\(error)") diff --git a/Tests/WATTests/Spectest.swift b/Tests/WATTests/Spectest.swift index 738de143..1cf76e65 100644 --- a/Tests/WATTests/Spectest.swift +++ b/Tests/WATTests/Spectest.swift @@ -1,4 +1,5 @@ import Foundation +import WasmParser enum Spectest { static let rootDirectory = URL(fileURLWithPath: #filePath) @@ -37,6 +38,14 @@ enum Spectest { } } + static func deriveFeatureSet(wast: URL) -> WasmFeatureSet { + var features = WasmFeatureSet.default + if wast.deletingLastPathComponent().path.hasSuffix("proposals/memory64") { + features.insert(.memory64) + } + return features + } + struct Command: Decodable { enum CommandType: String, Decodable { case module diff --git a/Tests/WATTests/TestSupport.swift b/Tests/WATTests/TestSupport.swift index 34944945..41a677fe 100644 --- a/Tests/WATTests/TestSupport.swift +++ b/Tests/WATTests/TestSupport.swift @@ -44,6 +44,14 @@ enum TestSupport { } static func lookupExecutable(_ name: String) -> URL? { + let envName = "\(name.uppercased())_EXEC" + if let path = ProcessInfo.processInfo.environment[envName] { + let url = URL(fileURLWithPath: path).deletingLastPathComponent().appendingPathComponent(name) + if FileManager.default.isExecutableFile(atPath: url.path) { + return url + } + print("Executable path \(url.path) specified by \(envName) is not found or not executable, falling back to PATH lookup") + } #if os(Windows) let pathEnvVar = "Path" let pathSeparator: Character = ";" diff --git a/Tests/WasmKitTests/FuzzTranslatorRegressionTests.swift b/Tests/WasmKitTests/FuzzTranslatorRegressionTests.swift index 9318f32a..aeb2eca6 100644 --- a/Tests/WasmKitTests/FuzzTranslatorRegressionTests.swift +++ b/Tests/WasmKitTests/FuzzTranslatorRegressionTests.swift @@ -14,8 +14,28 @@ final class FuzzTranslatorRegressionTests: XCTestCase { let data = try Data(contentsOf: URL(fileURLWithPath: path)) do { - var module = try WasmKit.parseWasm(bytes: Array(data)) - try module.materializeAll() + let module = try WasmKit.parseWasm(bytes: Array(data)) + let engine = Engine(configuration: EngineConfiguration(compilationMode: .eager)) + let store = Store(engine: engine) + var imports = Imports() + for importEntry in module.imports { + let value: ExternalValueConvertible + switch importEntry.descriptor { + case .function(let typeIndex): + let type = module.types[Int(typeIndex)] + value = Function(store: store, type: type) { _, _ in + fatalError("unreachable") + } + case .global(let globalType): + value = Global(store: store, type: globalType, value: .i32(0)) + case .memory(let memoryType): + value = try Memory(store: store, type: memoryType) + case .table(let tableType): + value = try Table(store: store, type: tableType) + } + imports.define(module: importEntry.module, name: importEntry.name, value.externalValue) + } + _ = try module.instantiate(store: store, imports: imports) } catch { // Explicit errors are ok } diff --git a/Tests/WasmKitTests/Spectest/TestCase.swift b/Tests/WasmKitTests/Spectest/TestCase.swift index b26a94f7..316ac1da 100644 --- a/Tests/WasmKitTests/Spectest/TestCase.swift +++ b/Tests/WasmKitTests/Spectest/TestCase.swift @@ -225,14 +225,11 @@ extension WastRunContext { case .assertMalformed(let module, let message): currentInstance = nil - guard case .binary = module.source else { - return .skipped("assert_malformed is only supported for binary modules for now") - } - do { - var module = try parseModule(rootPath: rootPath, moduleSource: module.source) + let module = try parseModule(rootPath: rootPath, moduleSource: module.source) + let instance = try instantiate(module: module) // Materialize all functions to see all errors in the module - try module.materializeAll() + try instance.handle.withValue { try $0.compileAllFunctions(store: store) } } catch { return .passed }