Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b2c5474
Tests: Enable `assert_invalid` tests
kateinoigakukun Oct 3, 2024
b2b64e7
WasmGen: Remove `Output` assoctype from InstructionVisitor
kateinoigakukun Oct 3, 2024
3c3a0d8
WasmGen: Split `InstructionEncoder` to a separate file
kateinoigakukun Oct 3, 2024
66eef6f
Validation: Validate memory alignment
kateinoigakukun Oct 3, 2024
9567f82
Validation: Check stack height at the end of block frames
kateinoigakukun Oct 3, 2024
a3c2917
Fix shifted register printing
kateinoigakukun Oct 3, 2024
a91a9d1
Translator: Fix a miscompilation for unreachable br_table
kateinoigakukun Oct 3, 2024
f0bcdb5
Validation: Ban internal global usage in constant expressions
kateinoigakukun Oct 3, 2024
788e27e
Validation: Check element segment
kateinoigakukun Oct 3, 2024
673fe9d
Validation: Check for duplicate export names
kateinoigakukun Oct 3, 2024
ae3902a
Validation: Check function type index bounds in import entry
kateinoigakukun Oct 3, 2024
4960d02
Validation: Check global mutability and types
kateinoigakukun Oct 3, 2024
d30ecca
CLI: Add wat2wasm command
kateinoigakukun Oct 3, 2024
c91dfb4
Revert "CLI: Add wat2wasm command"
kateinoigakukun Oct 3, 2024
7e46572
Validation: Fix unreachable `if` block with `else`
kateinoigakukun Oct 3, 2024
f8d9e35
Validation: Multiple memories are not permitted for now
kateinoigakukun Oct 3, 2024
5283139
WasmParser: Add `ExpressionParser` API
kateinoigakukun Oct 3, 2024
125e91c
Validation: Fix `end` instruction handling
kateinoigakukun Oct 3, 2024
c554be0
Validation: Add memory type validation
kateinoigakukun Oct 3, 2024
d452f05
Validation: Fix mixed memory64 table copy
kateinoigakukun Oct 3, 2024
baf442b
Validation: Check `table.init` instruction
kateinoigakukun Oct 4, 2024
0b94855
Explore: Print instruction offsets in disassembly
kateinoigakukun Oct 6, 2024
0129708
Translator: Fix a miscompilation bug in the return instruction
kateinoigakukun Oct 6, 2024
6bd7139
Validation: Ban function references to undeclared functions
kateinoigakukun Oct 6, 2024
d676fa4
Validation: Disallow `select` on reference types
kateinoigakukun Oct 6, 2024
5a6266c
Validation: Check start function type
kateinoigakukun Oct 6, 2024
18c6e61
WAT: Fix element expression encoding
kateinoigakukun Oct 6, 2024
8a0451c
Validation: Check table element type in table.copy
kateinoigakukun Oct 6, 2024
ea50c40
Validation: Check table types
kateinoigakukun Oct 6, 2024
5f2875c
Validation: Fix `br_table` instruction type checking
kateinoigakukun Oct 6, 2024
0d66e17
Validation: Check result types at end of root block
kateinoigakukun Oct 6, 2024
0a9fa7b
Validation: Push value on local.tee even if the frame is unreachable
kateinoigakukun Oct 6, 2024
e2d15f3
Validation: Fix `br_table` branch copy check
kateinoigakukun Oct 6, 2024
c26db63
Tests: Adjust ExecutionTests for the strict validation
kateinoigakukun Oct 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions Sources/WAT/Encoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,6 @@ struct ElementExprCollector: AnyInstructionVisitor {
typealias Output = Void

var isAllRefFunc: Bool = true
var useExpression: Bool {
// if all instructions are ref.func, use function indices representation
return !isAllRefFunc
}
var instructions: [Instruction] = []

mutating func parse(indices: WatParser.ElementDecl.Indices, wat: inout Wat) throws {
Expand Down Expand Up @@ -234,8 +230,11 @@ extension WAT.WatParser.ElementDecl {

var collector = ElementExprCollector()
try collector.parse(indices: indices, wat: &wat)

if collector.useExpression {
var useExpression: Bool {
// if all instructions are ref.func, use function indices representation
return !collector.isAllRefFunc || self.type != .funcRef
}
if useExpression {
// use expression
flags |= 0b0100
}
Expand All @@ -262,15 +261,15 @@ extension WAT.WatParser.ElementDecl {
}
}
if isPassive || hasTableIndex {
if collector.useExpression {
if useExpression {
encoder.encode(type)
} else {
// Write ExternKind.func
encoder.writeUnsignedLEB128(UInt8(0x00))
}
}

if collector.useExpression {
if useExpression {
try encoder.encodeVector(collector.instructions) { instruction, encoder in
var exprEncoder = ExpressionEncoder()
switch instruction {
Expand Down
411 changes: 411 additions & 0 deletions Sources/WAT/InstructionEncoder.swift

Large diffs are not rendered by default.

403 changes: 1 addition & 402 deletions Sources/WAT/ParseInstruction.swift

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Sources/WAT/Parser/ExpressionParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ struct ExpressionParser<Visitor: InstructionVisitor> {
var wat = Wat.empty(features: features)
// WAST allows extra const value instruction
if try parser.takeParenBlockStart("ref.extern") {
_ = try visitor.visitRefExtern(value: parser.expectUnsignedInt())
try visitor.visitRefExtern(value: parser.expectUnsignedInt())
try parser.expect(.rightParen)
return true
}
Expand Down Expand Up @@ -182,7 +182,7 @@ struct ExpressionParser<Visitor: InstructionVisitor> {
}

private struct Suspense {
let visit: ((inout Visitor, inout ExpressionParser) throws -> Visitor.Output)?
let visit: ((inout Visitor, inout ExpressionParser) throws -> Void)?
}

private mutating func foldedInstruction(visitor: inout Visitor, wat: inout Wat) throws -> Bool {
Expand Down Expand Up @@ -256,7 +256,7 @@ struct ExpressionParser<Visitor: InstructionVisitor> {
}

/// 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 -> Void) {
switch keyword {
case "select":
// Special handling for "select", which have two variants 1. with type, 2. without type
Expand Down
4 changes: 2 additions & 2 deletions Sources/WAT/Parser/WastParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import WasmParser
import WasmTypes

protocol WastConstInstructionVisitor: InstructionVisitor {
mutating func visitRefExtern(value: UInt32) throws -> Output
mutating func visitRefExtern(value: UInt32) throws
}

/// A parser for WAST format.
Expand Down Expand Up @@ -53,7 +53,7 @@ struct WastParser {
return result
}

struct ConstExpressionCollector: VoidInstructionVisitor, WastConstInstructionVisitor {
struct ConstExpressionCollector: WastConstInstructionVisitor {
let addValue: (Value) -> Void

mutating func visitI32Const(value: Int32) throws { addValue(.i32(UInt32(bitPattern: value))) }
Expand Down
1 change: 1 addition & 0 deletions Sources/WasmKit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ add_wasmkit_library(WasmKit
Module.swift
ModuleParser.swift
Translator.swift
Validator.swift
ResourceLimiter.swift
Component/CanonicalLifting.swift
Component/CanonicalLowering.swift
Expand Down
108 changes: 80 additions & 28 deletions Sources/WasmKit/Execution/ConstEvaluation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,33 @@ protocol ConstEvaluationContextProtocol {
func globalValue(_ index: GlobalIndex) throws -> Value
}

extension InternalInstance: ConstEvaluationContextProtocol {
func functionRef(_ index: FunctionIndex) throws -> Reference {
return try .function(from: self.functions[validating: Int(index)])
}
func globalValue(_ index: GlobalIndex) throws -> Value {
return try self.globals[validating: Int(index)].value
}
}

struct ConstEvaluationContext: ConstEvaluationContextProtocol {
let functions: ImmutableArray<InternalFunction>
var globals: [Value]
let onFunctionReferenced: ((InternalFunction) -> Void)?

init(
functions: ImmutableArray<InternalFunction>,
globals: [Value],
onFunctionReferenced: ((InternalFunction) -> Void)? = nil
) {
self.functions = functions
self.globals = globals
self.onFunctionReferenced = onFunctionReferenced
}

init(instance: InternalInstance, moduleImports: ModuleImports) {
// Constant expressions can only reference imported globals
let externalGlobals = instance.globals
.prefix(moduleImports.numberOfGlobals)
.map { $0.value }
self.init(functions: instance.functions, globals: Array(externalGlobals))
}

func functionRef(_ index: FunctionIndex) throws -> Reference {
return try .function(from: self.functions[validating: Int(index)])
let function = try self.functions[validating: Int(index)]
self.onFunctionReferenced?(function)
return .function(from: function)
}
func globalValue(_ index: GlobalIndex) throws -> Value {
guard index < globals.count else {
Expand All @@ -29,7 +42,13 @@ struct ConstEvaluationContext: ConstEvaluationContextProtocol {
}

extension ConstExpression {
func evaluate<C: ConstEvaluationContextProtocol>(context: C) throws -> Value {
func evaluate<C: ConstEvaluationContextProtocol>(context: C, expectedType: WasmTypes.ValueType) throws -> Value {
let result = try self._evaluate(context: context)
try result.checkType(expectedType)
return result
}

private func _evaluate<C: ConstEvaluationContextProtocol>(context: C) throws -> Value {
guard self.last == .end, self.count == 2 else {
throw InstantiationError.unsupported("Expect `end` at the end of offset expression")
}
Expand All @@ -56,25 +75,58 @@ extension ConstExpression {

extension WasmParser.ElementSegment {
func evaluateInits<C: ConstEvaluationContextProtocol>(context: C) throws -> [Reference] {
try self.initializer.map { expression -> Reference in
switch expression[0] {
case let .refFunc(index):
return try context.functionRef(index)
case .refNull(.funcRef):
return .function(nil)
case .refNull(.externRef):
return .extern(nil)
case .globalGet(let index):
let value = try context.globalValue(index)
switch value {
case .ref(.function(let addr)):
return .function(addr)
default:
throw Trap._raw("Unexpected global value type for element initializer expression")
}
return try self.initializer.map { expression -> Reference in
let result = try Self._evaluateInits(context: context, expression: expression)
try result.checkType(self.type)
return result
}
}
static func _evaluateInits<C: ConstEvaluationContextProtocol>(
context: C, expression: ConstExpression
) throws -> Reference {
switch expression[0] {
case let .refFunc(index):
return try context.functionRef(index)
case .refNull(.funcRef):
return .function(nil)
case .refNull(.externRef):
return .extern(nil)
case .globalGet(let index):
let value = try context.globalValue(index)
switch value {
case .ref(.function(let addr)):
return .function(addr)
default:
throw Trap._raw("Unexpected element initializer expression: \(expression)")
throw Trap._raw("Unexpected global value type for element initializer expression")
}
default:
throw Trap._raw("Unexpected element initializer expression: \(expression)")
}
}
}

fileprivate extension WasmTypes.Reference {
func checkType(_ type: WasmTypes.ReferenceType) throws {
switch (self, type) {
case (.function, .funcRef): return
case (.extern, .externRef): return
default:
throw ValidationError("Expect \(type) but got \(self)")
}
}
}

fileprivate extension Value {
func checkType(_ type: WasmTypes.ValueType) throws {
switch (self, type) {
case (.i32, .i32): return
case (.i64, .i64): return
case (.f32, .f32): return
case (.f64, .f64): return
case (.ref(let ref), .ref(let refType)):
try ref.checkType(refType)
default:
throw ValidationError("Expect \(type) but got \(self)")
}
}
}
12 changes: 11 additions & 1 deletion Sources/WasmKit/Execution/Instances.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ struct InstanceEntity /* : ~Copyable */ {
var elementSegments: ImmutableArray<InternalElementSegment>
var dataSegments: ImmutableArray<InternalDataSegment>
var exports: [String: InternalExternalValue]
var functionRefs: Set<InternalFunction>
var features: WasmFeatureSet
var hasDataCount: Bool

Expand All @@ -89,6 +90,7 @@ struct InstanceEntity /* : ~Copyable */ {
elementSegments: ImmutableArray(),
dataSegments: ImmutableArray(),
exports: [:],
functionRefs: [],
features: [],
hasDataCount: false
)
Expand Down Expand Up @@ -262,6 +264,10 @@ struct TableEntity /* : ~Copyable */ {
let tableType: TableType
var limits: Limits { tableType.limits }

static func maxSize(isMemory64: Bool) -> UInt64 {
return UInt64(UInt32.max)
}

init(_ tableType: TableType, resourceLimiter: any ResourceLimiter) throws {
let emptyElement: Reference
switch tableType.elementType {
Expand Down Expand Up @@ -418,6 +424,10 @@ public struct Table: Equatable {
struct MemoryEntity /* : ~Copyable */ {
static let pageSize = 64 * 1024

static func maxPageCount(isMemory64: Bool) -> UInt64 {
isMemory64 ? UInt64.max : UInt64(1 << 32) / UInt64(pageSize)
}

var data: [UInt8]
let maxPageCount: UInt64
let limit: Limits
Expand All @@ -428,7 +438,7 @@ struct MemoryEntity /* : ~Copyable */ {
throw Trap._raw("Initial memory size exceeds the resource limit: \(byteSize) bytes")
}
data = Array(repeating: 0, count: byteSize)
let defaultMaxPageCount = (memoryType.isMemory64 ? UInt64.max : UInt64(UInt32.max)) / UInt64(Self.pageSize)
let defaultMaxPageCount = Self.maxPageCount(isMemory64: memoryType.isMemory64)
maxPageCount = memoryType.max ?? defaultMaxPageCount
limit = memoryType
}
Expand Down
25 changes: 19 additions & 6 deletions Sources/WasmKit/Execution/Instructions/InstructionSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ protocol ShiftedVReg {

/// A larger (32-bit) version of `VReg`
/// Used to utilize halfword loads instructions.
struct LVReg: Equatable, ShiftedVReg {
struct LVReg: Equatable, ShiftedVReg, CustomStringConvertible {
let value: Int32

init(_ value: VReg) {
Expand All @@ -26,11 +26,15 @@ struct LVReg: Equatable, ShiftedVReg {
init(storage: Int32) {
self.value = storage
}

var description: String {
"\(value / Int32(MemoryLayout<StackSlot>.size))"
}
}

/// A larger (64-bit) version of `VReg`
/// Used to utilize word loads instructions.
struct LLVReg: Equatable, ShiftedVReg {
struct LLVReg: Equatable, ShiftedVReg, CustomStringConvertible {
let value: Int64

init(_ value: VReg) {
Expand All @@ -42,6 +46,10 @@ struct LLVReg: Equatable, ShiftedVReg {
init(storage: Int64) {
self.value = storage
}

var description: String {
"\(value / Int64(MemoryLayout<StackSlot>.size))"
}
}

// MARK: - Immediate load/emit support
Expand Down Expand Up @@ -218,7 +226,11 @@ extension InstructionSequence {
}
target.write("0x\(hexOffset): ")
let instruction = Instruction.load(from: &cursor)
context.print(instruction: instruction, to: &target)
context.print(
instruction: instruction,
instructionOffset: cursor - cursorStart,
to: &target
)
target.write("\n")
}
}
Expand All @@ -238,8 +250,7 @@ struct InstructionPrintingContext {
return "reg:\(reg)"
}
}
func reg(_ x: LVReg) -> String { reg(x.value) }
func reg(_ x: LLVReg) -> String { reg(x.value) }
func reg<R: ShiftedVReg>(_ x: R) -> String { reg(Int(x.value) / MemoryLayout<StackSlot>.size) }

func offset(_ offset: UInt64) -> String {
"offset: \(offset)"
Expand All @@ -264,6 +275,7 @@ struct InstructionPrintingContext {

mutating func print<Target>(
instruction: Instruction,
instructionOffset: Int,
to target: inout Target
) where Target : TextOutputStream {
switch instruction {
Expand Down Expand Up @@ -310,7 +322,8 @@ struct InstructionPrintingContext {
case .brIf(let op):
target.write("br_if \(reg(op.condition)), +\(op.offset)")
case .br(let offset):
target.write("br \(offset > 0 ? "+" : "")\(offset)")
let iseqOffset = instructionOffset + Int(offset)
target.write("br \(offset > 0 ? "+" : "")\(offset) ; 0x\(String(iseqOffset, radix: 16))")
case .brTable(let table):
target.write("br_table \(reg(table.index)), \(table.count) cases")
for i in 0..<table.count {
Expand Down
Loading