Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
116 changes: 104 additions & 12 deletions Sources/WasmKit/Execution/Instances.swift
Original file line number Diff line number Diff line change
Expand Up @@ -300,22 +300,54 @@ struct TableEntity /* : ~Copyable */ {
return true
}

mutating func initialize(elements source: [Reference], from fromIndex: Int, to toIndex: Int, count: Int) throws {
guard count > 0 else { return }
mutating func initialize(_ segment: InternalElementSegment, from source: Int, to destination: Int, count: Int) throws {
try self.initialize(segment.references, from: source, to: destination, count: count)
}

guard !fromIndex.addingReportingOverflow(count).overflow,
!toIndex.addingReportingOverflow(count).overflow
else {
throw Trap.tableSizeOverflow
}
mutating func initialize(_ references: [Reference], from source: Int, to destination: Int, count: Int) throws {
let (destinationEnd, destinationOverflow) = destination.addingReportingOverflow(count)
let (sourceEnd, sourceOverflow) = source.addingReportingOverflow(count)

guard fromIndex + count <= source.count else {
throw Trap.outOfBoundsTableAccess(fromIndex + count)
guard !destinationOverflow, !sourceOverflow else { throw Trap.tableSizeOverflow }
guard destinationEnd <= elements.count else { throw Trap.outOfBoundsTableAccess(destinationEnd) }
guard sourceEnd <= references.count else { throw Trap.outOfBoundsTableAccess(sourceEnd) }

elements.withUnsafeMutableBufferPointer { table in
references.withUnsafeBufferPointer { segment in
_ = table[destination..<destination + count].initialize(from: segment[source..<source + count])
}
}
guard toIndex + count <= self.elements.count else {
throw Trap.outOfBoundsTableAccess(toIndex + count)
}

mutating func fill(repeating value: Reference, from index: Int, count: Int) throws {
let (end, overflow) = index.addingReportingOverflow(count)
guard !overflow, end <= elements.count else { throw Trap.tableSizeOverflow }

elements.withUnsafeMutableBufferPointer {
$0[index..<index + count].initialize(repeating: value)
}
elements[toIndex..<(toIndex + count)] = source[fromIndex..<fromIndex + count]
}

static func copy(
_ sourceTable: UnsafeBufferPointer<Reference>,
_ destinationTable: UnsafeMutableBufferPointer<Reference>,
from source: Int, to destination: Int, count: Int
) throws {
let (destinationEnd, destinationOverflow) = destination.addingReportingOverflow(count)
let (sourceEnd, sourceOverflow) = source.addingReportingOverflow(count)

guard !destinationOverflow, !sourceOverflow else { throw Trap.tableSizeOverflow }
guard destinationEnd <= destinationTable.count else { throw Trap.outOfBoundsTableAccess(Int(destinationEnd)) }
guard sourceEnd <= sourceTable.count else { throw Trap.outOfBoundsTableAccess(Int(sourceEnd)) }

let source = UnsafeBufferPointer(rebasing: sourceTable[source..<source + count])
let destination = UnsafeMutableBufferPointer(rebasing: destinationTable[destination..<destination + count])

// Note: Do not use `UnsafeMutableBufferPointer.update(from:)` overload here because it does not
// provide the same semantics as `memmove` for overlapping memory regions.
// TODO: We can optimize this to use `memcpy` if the source and destination tables are known to be different
// at translation time.
_ = destination.update(fromContentsOf: source)
}
}

Expand All @@ -327,6 +359,30 @@ extension TableEntity: ValidatableEntity {

typealias InternalTable = EntityHandle<TableEntity>

extension InternalTable {
func copy(_ sourceTable: InternalTable, from source: Int, to destination: Int, count: Int) throws {
// Check if the source and destination tables are the same for dynamic exclusive
// access enforcement
if self == sourceTable {
try withValue {
try $0.elements.withUnsafeMutableBufferPointer {
try TableEntity.copy(UnsafeBufferPointer($0), $0, from: source, to: destination, count: count)
}
}
} else {
try withValue { destinationTable in
try sourceTable.withValue { sourceTable in
try destinationTable.elements.withUnsafeMutableBufferPointer { dest in
try sourceTable.elements.withUnsafeBufferPointer { src in
try TableEntity.copy(src, dest, from: source, to: destination, count: count)
}
}
}
}
}
}
}

/// A WebAssembly `table` instance.
/// > Note:
/// <https://webassembly.github.io/spec/core/exec/runtime.html#table-instances>
Expand Down Expand Up @@ -395,6 +451,42 @@ struct MemoryEntity /* : ~Copyable */ {
return limit.isMemory64 ? .i64(UInt64(result)) : .i32(result)
}

mutating func copy(from source: UInt64, to destination: UInt64, count: UInt64) throws {
let (destinationEnd, destinationOverflow) = destination.addingReportingOverflow(count)
let (sourceEnd, sourceOverflow) = source.addingReportingOverflow(count)

guard !destinationOverflow, destinationEnd <= data.count,
!sourceOverflow, sourceEnd <= data.count else {
throw Trap.outOfBoundsMemoryAccess
}
data.withUnsafeMutableBufferPointer {
guard let base = UnsafeMutableRawPointer($0.baseAddress) else { return }
let dest = base.advanced(by: Int(destination))
let src = base.advanced(by: Int(source))
dest.copyMemory(from: src, byteCount: Int(count))
}
}

mutating func initialize(_ segment: InternalDataSegment, from source: UInt32, to destination: UInt64, count: UInt32) throws {
let (destinationEnd, destinationOverflow) = destination.addingReportingOverflow(UInt64(count))
let (sourceEnd, sourceOverflow) = source.addingReportingOverflow(count)

guard !destinationOverflow, destinationEnd <= data.count,
!sourceOverflow, sourceEnd <= segment.data.count else {
throw Trap.outOfBoundsMemoryAccess
}
data.withUnsafeMutableBufferPointer { memory in
segment.data.withUnsafeBufferPointer { segment in
guard
let memory = UnsafeMutableRawPointer(memory.baseAddress),
let segment = UnsafeRawPointer(segment.baseAddress) else { return }
let dest = memory.advanced(by: Int(destination))
let src = segment.advanced(by: Int(source))
dest.copyMemory(from: src, byteCount: Int(count))
}
}
}

mutating func write(offset: Int, bytes: ArraySlice<UInt8>) throws {
let endOffset = offset + bytes.count
guard endOffset <= data.count else {
Expand Down
56 changes: 10 additions & 46 deletions Sources/WasmKit/Execution/Instructions/Memory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,29 +58,13 @@ extension Execution {
mutating func memoryInit(sp: Sp, immediate: Instruction.MemoryInitOperand) throws {
let instance = currentInstance(sp: sp)
let memory = instance.memories[0]
try memory.withValue { memoryInstance in
let dataInstance = instance.dataSegments[Int(immediate.segmentIndex)]

let copyCounter = sp[immediate.size].i32
let sourceIndex = sp[immediate.sourceOffset].i32
let destinationIndex = sp[immediate.destOffset].asAddressOffset(memoryInstance.limit.isMemory64)

guard copyCounter > 0 else { return }

guard
!sourceIndex.addingReportingOverflow(copyCounter).overflow
&& !destinationIndex.addingReportingOverflow(UInt64(copyCounter)).overflow
&& memoryInstance.data.count >= destinationIndex + UInt64(copyCounter)
&& dataInstance.data.count >= sourceIndex + copyCounter
else {
throw Trap.outOfBoundsMemoryAccess
}
try memory.withValue { memory in
let segment = instance.dataSegments[Int(immediate.segmentIndex)]

// FIXME: benchmark if using `replaceSubrange` is faster than this loop
for i in 0..<copyCounter {
memoryInstance.data[Int(destinationIndex + UInt64(i))] =
dataInstance.data[dataInstance.data.startIndex + Int(sourceIndex + i)]
}
let size = sp[immediate.size].i32
let source = sp[immediate.sourceOffset].i32
let destination = sp[immediate.destOffset].asAddressOffset(memory.limit.isMemory64)
try memory.initialize(segment, from: source, to: destination, count: size)
}
}
mutating func memoryDataDrop(sp: Sp, immediate: Instruction.MemoryDataDropOperand) {
Expand All @@ -91,30 +75,10 @@ extension Execution {
let memory = currentInstance(sp: sp).memories[0]
try memory.withValue { memory in
let isMemory64 = memory.limit.isMemory64
let copyCounter = sp[immediate.size].asAddressOffset(isMemory64)
let sourceIndex = sp[immediate.sourceOffset].asAddressOffset(isMemory64)
let destinationIndex = sp[immediate.destOffset].asAddressOffset(isMemory64)

guard copyCounter > 0 else { return }

guard
!sourceIndex.addingReportingOverflow(copyCounter).overflow
&& !destinationIndex.addingReportingOverflow(copyCounter).overflow
&& memory.data.count >= destinationIndex + copyCounter
&& memory.data.count >= sourceIndex + copyCounter
else {
throw Trap.outOfBoundsMemoryAccess
}

if destinationIndex <= sourceIndex {
for i in 0..<copyCounter {
memory.data[Int(destinationIndex + i)] = memory.data[Int(sourceIndex + i)]
}
} else {
for i in 1...copyCounter {
memory.data[Int(destinationIndex + copyCounter - i)] = memory.data[Int(sourceIndex + copyCounter - i)]
}
}
let size = sp[immediate.size].asAddressOffset(isMemory64)
let source = sp[immediate.sourceOffset].asAddressOffset(isMemory64)
let destination = sp[immediate.destOffset].asAddressOffset(isMemory64)
try memory.copy(from: source, to: destination, count: size)
}
}
mutating func memoryFill(sp: Sp, immediate: Instruction.MemoryFillOperand) throws {
Expand Down
41 changes: 4 additions & 37 deletions Sources/WasmKit/Execution/Instructions/Table.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,7 @@ extension Execution {
let fillValue = sp.getReference(immediate.value, type: table.tableType)
let startIndex = sp[immediate.destOffset].asAddressOffset(table.limits.isMemory64)

guard fillCounter > 0 else {
return
}

guard Int(startIndex + fillCounter) <= table.elements.count else {
throw Trap.outOfBoundsTableAccess(Int(startIndex + fillCounter))
}

for i in 0..<fillCounter {
setTableElement(table: table, Int(startIndex + i), fillValue)
}
try table.withValue { try $0.fill(repeating: fillValue, from: Int(startIndex), count: Int(fillCounter)) }
}
mutating func tableCopy(sp: Sp, immediate: Instruction.TableCopyOperand) throws {
let sourceTableIndex = immediate.sourceIndex
Expand All @@ -61,36 +51,13 @@ extension Execution {
let sourceTable = getTable(sourceTableIndex, sp: sp, store: store)
let destinationTable = getTable(destinationTableIndex, sp: sp, store: store)

let copyCounter = sp[immediate.size].asAddressOffset(
let size = sp[immediate.size].asAddressOffset(
sourceTable.limits.isMemory64 || destinationTable.limits.isMemory64
)
let sourceIndex = sp[immediate.sourceOffset].asAddressOffset(sourceTable.limits.isMemory64)
let destinationIndex = sp[immediate.destOffset].asAddressOffset(destinationTable.limits.isMemory64)

guard copyCounter > 0 else {
return
}

guard
!sourceIndex.addingReportingOverflow(copyCounter).overflow && !destinationIndex.addingReportingOverflow(copyCounter).overflow
else {
throw Trap.tableSizeOverflow
}
guard destinationIndex + copyCounter <= sourceTable.elements.count else {
throw Trap.outOfBoundsTableAccess(Int(destinationIndex + copyCounter))
}
guard destinationIndex + copyCounter <= sourceTable.elements.count && sourceIndex + copyCounter <= destinationTable.elements.count else {
throw Trap.outOfBoundsTableAccess(Int(destinationIndex + copyCounter))
}

let valuesToCopy = Array(sourceTable.elements[Int(sourceIndex)..<Int(sourceIndex + copyCounter)])
for (i, value) in valuesToCopy.enumerated() {
setTableElement(
table: destinationTable,
Int(destinationIndex) + i,
value
)
}
try destinationTable.copy(sourceTable, from: Int(sourceIndex), to: Int(destinationIndex), count: Int(size))
}
mutating func tableInit(sp: Sp, immediate: Instruction.TableInitOperand) throws {
let tableIndex = immediate.tableIndex
Expand All @@ -104,7 +71,7 @@ extension Execution {

try destinationTable.withValue {
try $0.initialize(
elements: sourceElement.references,
sourceElement,
from: Int(sourceIndex), to: Int(destinationIndex),
count: Int(copyCounter)
)
Expand Down
2 changes: 1 addition & 1 deletion Sources/WasmKit/Module.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public struct Module {
}
let references = try element.evaluateInits(context: instance)
try table.initialize(
elements: references, from: 0, to: Int(offset), count: references.count
references, from: 0, to: Int(offset), count: references.count
)
}
}
Expand Down
12 changes: 8 additions & 4 deletions Tests/WasmKitTests/Spectest/Spectest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import SystemPackage
import WAT
import WasmKit

private func loadStringArrayFromEnvironment(_ key: String) -> [String] {
ProcessInfo.processInfo.environment[key]?.split(separator: ",").map(String.init) ?? []
}

@available(macOS 11, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
public func spectest(
path: [String],
include: String?,
exclude: String?,
include: [String]? = nil,
exclude: [String]? = nil,
verbose: Bool = false,
parallel: Bool = true,
configuration: EngineConfiguration = .init()
Expand All @@ -28,8 +32,8 @@ public func spectest(
"\(Int(Double(numerator) / Double(denominator) * 100))%"
}

let include = include.flatMap { $0.split(separator: ",").map(String.init) } ?? []
let exclude = exclude.flatMap { $0.split(separator: ",").map(String.init) } ?? []
let include = include ?? loadStringArrayFromEnvironment("WASMKIT_SPECTEST_INCLUDE")
let exclude = exclude ?? loadStringArrayFromEnvironment("WASMKIT_SPECTEST_EXCLUDE")

let testCases: [TestCase]
do {
Expand Down
Loading