Skip to content

Commit d7a9719

Browse files
authored
Merge pull request #7 from p-x9/feature/fileio
Improve performance of file I/O using mmap
2 parents 43f4222 + a2a5c6e commit d7a9719

19 files changed

+516
-117
lines changed

Package.resolved

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ let package = Package(
1010
targets: ["ELFKit"]
1111
),
1212
],
13+
dependencies: [
14+
.package(url: "https://github.com/p-x9/swift-fileio.git", from: "0.9.0")
15+
],
1316
targets: [
1417
.target(
1518
name: "ELFKit",
1619
dependencies: [
17-
"ELFKitC"
20+
"ELFKitC",
21+
.product(name: "FileIO", package: "swift-fileio")
1822
]
1923
),
2024
.target(

Sources/ELFKit/ELFFile+Strings.swift

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
//
88

99
import Foundation
10+
#if compiler(>=6.0) || (compiler(>=5.10) && hasFeature(AccessLevelOnImport))
11+
internal import FileIO
12+
#else
13+
@_implementationOnly import FileIO
14+
#endif
1015

1116
public protocol StringTable<Encoding>: Sequence<StringTableEntry> {
1217
associatedtype Encoding: _UnicodeEncoding
@@ -16,18 +21,20 @@ extension ELFFile {
1621
public typealias Strings = UnicodeStrings<UTF8>
1722

1823
public struct UnicodeStrings<Encoding: _UnicodeEncoding>: StringTable {
19-
public let data: Data
24+
typealias FileSlice = File.FileSlice
25+
26+
private let fileSlice: FileSlice
2027

2128
/// file offset of string table start
2229
public let offset: Int
2330

2431
/// size of string table
2532
public let size: Int
2633

27-
public let isLittleEndian: Bool
34+
public let isSwapped: Bool
2835

2936
public func makeIterator() -> Iterator {
30-
.init(data: data, isLittleEndian: isLittleEndian)
37+
.init(fileSlice: fileSlice, isSwapped: isSwapped)
3138
}
3239
}
3340
}
@@ -37,78 +44,102 @@ extension ELFFile.UnicodeStrings {
3744
elf: ELFFile,
3845
offset: Int,
3946
size: Int,
40-
isLittleEndian: Bool = false
47+
isSwapped: Bool
4148
) {
42-
let data = elf.fileHandle.readData(
43-
offset: numericCast(offset),
44-
size: size
49+
let fileSlice = try! elf.fileHandle.fileSlice(
50+
offset: offset,
51+
length: size
4552
)
4653
self.init(
47-
data: data,
54+
fileSlice: fileSlice,
4855
offset: offset,
4956
size: size,
50-
isLittleEndian: isLittleEndian
57+
isSwapped: isSwapped
5158
)
5259
}
5360
}
5461

62+
extension ELFFile.UnicodeStrings {
63+
public var data: Data? {
64+
try? fileSlice.readAllData()
65+
}
66+
}
67+
5568
extension ELFFile.UnicodeStrings {
5669
public struct Iterator: IteratorProtocol {
5770
public typealias Element = StringTableEntry
5871

59-
private let data: Data
72+
private let fileSice: FileSlice
6073
private let tableSize: Int
61-
private let isLittleEndian: Bool
74+
private let isSwapped: Bool
6275

6376
private var nextOffset: Int
6477

65-
init(data: Data, isLittleEndian: Bool) {
66-
self.data = data
67-
self.tableSize = data.count
68-
self.isLittleEndian = isLittleEndian
78+
init(fileSlice: FileSlice, isSwapped: Bool) {
79+
self.fileSice = fileSlice
80+
self.tableSize = fileSlice.size
6981
self.nextOffset = 0
82+
self.isSwapped = isSwapped
7083
}
7184

7285
public mutating func next() -> Element? {
73-
data.withUnsafeBytes {
74-
if nextOffset >= tableSize { return nil }
75-
guard let baseAddress = $0.baseAddress else { return nil }
76-
77-
let ptr = baseAddress
78-
.advanced(by: nextOffset)
79-
.assumingMemoryBound(to: Encoding.CodeUnit.self)
80-
var (string, offset) = ptr.readString(as: Encoding.self)
81-
82-
if isLittleEndian {
83-
let data = Data(bytes: ptr, count: offset)
84-
string = data.withUnsafeBytes {
85-
let baseAddress = $0.baseAddress!
86-
.assumingMemoryBound(to: Encoding.CodeUnit.self)
87-
return .init(
88-
decodingCString: baseAddress,
89-
as: Encoding.self
90-
)
91-
}
92-
}
86+
guard nextOffset < tableSize else { return nil }
9387

94-
let result = Element(string: string, offset: nextOffset)
88+
let ptr = UnsafeRawPointer(fileSice.ptr)
89+
.advanced(by: nextOffset)
90+
.assumingMemoryBound(to: Encoding.CodeUnit.self)
91+
var (string, offset) = ptr.readString(as: Encoding.self)
9592

93+
defer {
9694
nextOffset += offset
95+
}
9796

98-
return result
97+
if isSwapped || shouldSwap(ptr) {
98+
let data = Data(bytes: ptr, count: offset)
99+
.byteSwapped(Encoding.CodeUnit.self)
100+
string = data.withUnsafeBytes {
101+
let baseAddress = $0.baseAddress!
102+
.assumingMemoryBound(to: Encoding.CodeUnit.self)
103+
return .init(
104+
decodingCString: baseAddress,
105+
as: Encoding.self
106+
)
107+
}
99108
}
109+
110+
return .init(
111+
string: string,
112+
offset: nextOffset
113+
)
100114
}
101115
}
102116
}
103117

104118
extension ELFFile.Strings {
105119
func string(at offset: Int) -> Element? {
106-
guard data.count >= offset else { return nil }
107-
guard let string = String(
108-
cString: data.advanced(by: offset)
109-
) else {
110-
return nil
111-
}
120+
guard fileSlice.size >= offset else { return nil }
121+
let string = String(
122+
cString: fileSlice.ptr
123+
.advanced(by: offset)
124+
.assumingMemoryBound(to: CChar.self)
125+
)
112126
return .init(string: string, offset: offset)
113127
}
114128
}
129+
130+
extension ELFFile.UnicodeStrings.Iterator {
131+
// https://github.com/swiftlang/swift-corelibs-foundation/blob/4a9694d396b34fb198f4c6dd865702f7dc0b0dcf/Sources/Foundation/NSString.swift#L1390
132+
func shouldSwap(_ ptr: UnsafePointer<Encoding.CodeUnit>) -> Bool {
133+
let size = MemoryLayout<Encoding.CodeUnit>.size
134+
switch size {
135+
case 1:
136+
return false
137+
case 2:
138+
return ptr.pointee == 0xFFFE /* ZERO WIDTH NO-BREAK SPACE (swapped) */
139+
case 4:
140+
return ptr.pointee == UInt32(0xFFFE0000) // avoid overflows in 32bit env
141+
default:
142+
return false
143+
}
144+
}
145+
}

Sources/ELFKit/ELFFile.swift

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@
77
//
88

99
import Foundation
10+
#if compiler(>=6.0) || (compiler(>=5.10) && hasFeature(AccessLevelOnImport))
11+
internal import FileIO
12+
#else
13+
@_implementationOnly import FileIO
14+
#endif
1015

1116
public final class ELFFile: ELFRepresentable {
17+
typealias File = MemoryMappedFile
18+
1219
/// URL of the file actually loaded
1320
public let url: URL
1421

15-
let fileHandle: FileHandle
22+
let fileHandle: File
1623

1724
/// A boolean value that indicates whether ELF is a 64-bit architecture.
1825
public var is64Bit: Bool { header.identifier.class == ._64 }
@@ -27,21 +34,24 @@ public final class ELFFile: ELFRepresentable {
2734

2835
public init(url: URL) throws {
2936
self.url = url
30-
self.fileHandle = try FileHandle(forReadingFrom: url)
37+
self.fileHandle = try File.open(
38+
url: url,
39+
isWritable: false
40+
)
3141

3242
guard let identifier: HeaderIdentifier = .init(
33-
layout: fileHandle.read(offset: 0)
43+
layout: try fileHandle.read(offset: 0)
3444
) else { throw ELFKitError.invalidFile }
3545

3646
let header: ELFHeader
3747
switch identifier.class {
3848
case ._32:
39-
let _header: ELF32Header = fileHandle.read(
49+
let _header: ELF32Header = try fileHandle.read(
4050
offset: 0
4151
)
4252
header = ._32(_header)
4353
case ._64:
44-
let _header: ELF64Header = fileHandle.read(
54+
let _header: ELF64Header = try fileHandle.read(
4555
offset: 0
4656
)
4757
header = ._64(_header)
@@ -51,10 +61,6 @@ public final class ELFFile: ELFRepresentable {
5161

5262
self.header = header
5363
}
54-
55-
deinit {
56-
fileHandle.closeFile()
57-
}
5864
}
5965

6066
extension ELFFile {

Sources/ELFKit/Extension/Data+.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// Data+.swift
3+
// MachOKit
4+
//
5+
// Created by p-x9 on 2025/02/02
6+
//
7+
//
8+
import Foundation
9+
10+
extension Data {
11+
func byteSwapped<T: FixedWidthInteger>(_ type: T.Type) -> Data {
12+
guard count >= MemoryLayout<T>.size else { return self }
13+
14+
let valueArray = self.withUnsafeBytes {
15+
Array($0.bindMemory(to: T.self))
16+
}
17+
18+
let swappedArray = valueArray.map { $0.byteSwapped }
19+
20+
var swappedData = swappedArray.withUnsafeBufferPointer {
21+
Data(buffer: $0)
22+
}
23+
24+
let remainingBytes = count % MemoryLayout<T>.size
25+
if remainingBytes > 0 {
26+
swappedData.append(self.suffix(remainingBytes))
27+
}
28+
29+
return swappedData
30+
}
31+
}

Sources/ELFKit/Extension/ELFFile+Dynamics32+.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ extension ELFFile.Dynamics32 {
4848
guard let offset = elf.fileOffset(of: _verdef.pointer) else {
4949
return nil
5050
}
51-
let layout: ELF32VersionDef.Layout = elf.fileHandle.read(
52-
offset: numericCast(offset)
51+
let layout: ELF32VersionDef.Layout = try! elf.fileHandle.read(
52+
offset: offset
5353
)
5454
return .init(
5555
layout: layout,
@@ -66,8 +66,8 @@ extension ELFFile.Dynamics32 {
6666
guard let offset = elf.fileOffset(of: _verneed.pointer) else {
6767
return nil
6868
}
69-
let layout: ELF32VersionNeed.Layout = elf.fileHandle.read(
70-
offset: numericCast(offset)
69+
let layout: ELF32VersionNeed.Layout = try! elf.fileHandle.read(
70+
offset: offset
7171
)
7272
return .init(
7373
layout: layout,

Sources/ELFKit/Extension/ELFFile+Dynamics64+.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ extension ELFFile.Dynamics64 {
4848
guard let offset = elf.fileOffset(of: _verdef.pointer) else {
4949
return nil
5050
}
51-
let layout: ELF64VersionDef.Layout = elf.fileHandle.read(
52-
offset: numericCast(offset)
51+
let layout: ELF64VersionDef.Layout = try! elf.fileHandle.read(
52+
offset: offset
5353
)
5454
return .init(
5555
layout: layout,
@@ -66,8 +66,8 @@ extension ELFFile.Dynamics64 {
6666
guard let offset = elf.fileOffset(of: _verneed.pointer) else {
6767
return nil
6868
}
69-
let layout: ELF64VersionNeed.Layout = elf.fileHandle.read(
70-
offset: numericCast(offset)
69+
let layout: ELF64VersionNeed.Layout = try! elf.fileHandle.read(
70+
offset: offset
7171
)
7272
return .init(
7373
layout: layout,

0 commit comments

Comments
 (0)