-
Notifications
You must be signed in to change notification settings - Fork 109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement FileDescriptor.memoryMap
, FileDescriptor.memoryUnmap
, and FileDescriptor.memorySync
#68
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -393,4 +393,181 @@ extension FileDescriptor { | |||||||||||||||||||||||
}.map { _ in (.init(rawValue: fds.0), .init(rawValue: fds.1)) } | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
#endif | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
#if !os(Windows) | ||||||||||||||||||||||||
// MARK: Memory Mapping | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// Describes the desired memory protection of the | ||||||||||||||||||||||||
/// mapping (and must not conflict with the open mode of the file). | ||||||||||||||||||||||||
/// Flags can be the bitwise OR of one or more of each case. | ||||||||||||||||||||||||
@frozen | ||||||||||||||||||||||||
public struct MemoryProtection: RawRepresentable, Hashable, Codable { | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to be an |
||||||||||||||||||||||||
/// The raw C protection number. | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public let rawValue: CInt | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// Creates a strongly typed error number from a raw C error number. | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public init(rawValue: CInt) { self.rawValue = rawValue } | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
private init(_ raw: CInt) { self.init(rawValue: raw) } | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// Pages may not be accessed. | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docs should specify the original C names of these constants (and types, where appropriate), to help orientate people who are already familiar with them, and to aid searching.
Suggested change
(This applies to all of these.) |
||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public static var none: MemoryProtection { MemoryProtection(rawValue: _PROT_NONE) } | ||||||||||||||||||||||||
/// Pages may be read. | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public static var read: MemoryProtection { MemoryProtection(rawValue: _PROT_READ) } | ||||||||||||||||||||||||
/// Pages may be written. | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public static var write: MemoryProtection { MemoryProtection(rawValue: _PROT_WRITE) } | ||||||||||||||||||||||||
/// Pages may be executed. | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public static var executed: MemoryProtection { MemoryProtection(rawValue: _PROT_EXEC) } | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// Determines whether updates to the mapping are | ||||||||||||||||||||||||
/// visible to other processes mapping the same region, and whether | ||||||||||||||||||||||||
/// updates are carried through to the underlying file. This | ||||||||||||||||||||||||
/// behavior is determined by exactly one flag. | ||||||||||||||||||||||||
public struct MemoryMapKind: RawRepresentable, Hashable, Codable { | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. POSIX calls these "flags"; we should follow the same terminology.
Suggested change
Like |
||||||||||||||||||||||||
/// The raw C flag number. | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public let rawValue: CInt | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// Creates a strongly typed error number from a raw C error number. | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs a revision. |
||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public init(rawValue: CInt) { self.rawValue = rawValue } | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
private init(_ raw: CInt) { self.init(rawValue: raw) } | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// Share this mapping. Updates to the mapping are visible to | ||||||||||||||||||||||||
/// other processes mapping the same region, and (in the case | ||||||||||||||||||||||||
/// of file-backed mappings) are carried through to the | ||||||||||||||||||||||||
/// underlying file. | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public static var shared: MemoryMapKind { MemoryMapKind(rawValue: _MAP_SHARED) } | ||||||||||||||||||||||||
/// Create a private copy-on-write mapping. Updates to the | ||||||||||||||||||||||||
/// mapping are not visible to other processes mapping the | ||||||||||||||||||||||||
/// same file, and are not carried through to the underlying | ||||||||||||||||||||||||
/// file. It is unspecified whether changes made to the file | ||||||||||||||||||||||||
/// after the `memoryMap` call are visible in the mapped region. | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public static var `private`: MemoryMapKind { MemoryMapKind(rawValue: _MAP_PRIVATE) } | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
// TODO: There are several other MemoryMapKinds. | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should default to exposing every flag that is present in the underlying system headers. (This will be different on every supported platform.) E.g., on Darwin, we have: #define MAP_FIXED 0x0010 /* [MF|SHM] interpret addr exactly */
#define MAP_RENAME 0x0020 /* Sun: rename private pages to file */
#define MAP_NORESERVE 0x0040 /* Sun: don't reserve needed swap area */
#define MAP_RESERVED0080 0x0080 /* previously unimplemented MAP_INHERIT */
#define MAP_NOEXTEND 0x0100 /* for MAP_FILE, don't change file size */
#define MAP_HASSEMAPHORE 0x0200 /* region may contain semaphores */
#define MAP_NOCACHE 0x0400 /* don't cache pages for this mapping */
#define MAP_JIT 0x0800 /* Allocate a region that will be used for JIT purposes */
#define MAP_FILE 0x0000 /* map from file (default) */
#define MAP_ANON 0x1000 /* allocated from memory, swap space */
#define MAP_ANONYMOUS MAP_ANON
#define MAP_RESILIENT_CODESIGN 0x2000 /* no code-signing failures */
#define MAP_RESILIENT_MEDIA 0x4000 /* no backing-store failures */
#define MAP_32BIT 0x8000 /* Return virtual addresses <4G only */
#define MAP_TRANSLATED_ALLOW_EXECUTE 0x20000 /* allow execute in translated processes */
#define MAP_UNIX03 0x40000 /* UNIX03 compliance */ |
||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// Determines whether memory sync should be | ||||||||||||||||||||||||
/// synchronous, asynchronous. | ||||||||||||||||||||||||
@frozen | ||||||||||||||||||||||||
public struct MemorySyncKind: RawRepresentable, Hashable, Codable { | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like |
||||||||||||||||||||||||
/// The raw C flag number. | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public let rawValue: CInt | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// Creates a strongly typed error number from a raw C error number. | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Revise |
||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public init(rawValue: CInt) { self.rawValue = rawValue } | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
private init(_ raw: CInt) { self.init(rawValue: raw) } | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// Requests an update and waits for it to complete. | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public static var synchronous: MemorySyncKind { MemorySyncKind(rawValue: _MS_SYNC) } | ||||||||||||||||||||||||
/// Specifies that an update be scheduled, but the call | ||||||||||||||||||||||||
/// returns immediately. | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public static var asynchronous: MemorySyncKind { MemorySyncKind(rawValue: _MS_ASYNC) } | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we also want On Darwin, we probably also want to expose |
||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// Create a new mapping in the virtual address space of the | ||||||||||||||||||||||||
/// calling process. | ||||||||||||||||||||||||
/// After the `memoryMap` call has returned, the file descriptor can | ||||||||||||||||||||||||
/// be closed immediately without invalidating the mapping. | ||||||||||||||||||||||||
/// - Parameters: | ||||||||||||||||||||||||
/// - length: Specifies the length of the mapping (which must be greater than 0). | ||||||||||||||||||||||||
/// - pageOffset: The page offset to map. Page size is determined by `sysconf(_SC_PAGE_SIZE)` | ||||||||||||||||||||||||
/// - kind: Determines the kind of mapping returned. Currently limited to `MAP_SHARED` and `MAP_PRIVATE`. | ||||||||||||||||||||||||
/// - protection: Describes the desired memory protection of the mapping (and must not conflict with the open mode of the file). | ||||||||||||||||||||||||
/// - Returns: The new memory mapping. | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public func memoryMap( | ||||||||||||||||||||||||
length: Int, pageOffset: Int, kind: MemoryMapKind, protection: [MemoryProtection] | ||||||||||||||||||||||||
) throws -> UnsafeMutableRawPointer { | ||||||||||||||||||||||||
Comment on lines
+498
to
+500
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs significant changes.
Here is one way to embrace these suggestions: public struct MemoryProtection { ... }
public struct MemoryMapFlags { ... }
extension FileDescriptor {
public func mapMemory(
at address: UnsafeMutableRawPointer? = nil,
from offset: Int64,
length: Int,
protection: MemoryProtection,
flags: MemoryMapFlags,
) throws -> UnsafeMutableRawPointer?
}
public struct MachVMFlags: RawRepresentable {
public var rawValue: CInt
public init?(rawValue: CInt)
public static var purgeable: Self { /* VM_FLAGS_PURGABLE */ }
public static func tag(_ value: UInt8) -> Self
}
public func mapAnonymousMemory(
at address: UnsafeMutableRawPointer? = nil,
length: Int,
protection: MemoryProtection,
flags: MemoryMapFlags,
machVMFlags: MachVMFlags? = nil
) throws -> UnsafeMutableRawPointer? (If we go in this direction, then both public variants must forward to the same Given that either |
||||||||||||||||||||||||
try _memoryMap(length: length, | ||||||||||||||||||||||||
pageOffset: pageOffset, | ||||||||||||||||||||||||
kind: kind, | ||||||||||||||||||||||||
protection: protection).get() | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
@usableFromInline | ||||||||||||||||||||||||
internal func _memoryMap( | ||||||||||||||||||||||||
length: Int, pageOffset: Int, kind: MemoryMapKind, protection: [MemoryProtection] | ||||||||||||||||||||||||
) throws -> Result<UnsafeMutableRawPointer, Errno> { | ||||||||||||||||||||||||
valueOrErrno(valueOnFail: _MAP_FAILED, retryOnInterrupt: false) { | ||||||||||||||||||||||||
system_mmap(self.rawValue, length, protection.reduce(into: Int32(), { partialResult, prot in | ||||||||||||||||||||||||
partialResult |= prot.rawValue | ||||||||||||||||||||||||
}), kind.rawValue, _COffT(pageOffset)) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// Deletes the mappings for the specified | ||||||||||||||||||||||||
/// mapping, and causes further references to addresses within | ||||||||||||||||||||||||
/// the range to generate invalid memory references. The region is | ||||||||||||||||||||||||
/// also automatically unmapped when the process is terminated. On | ||||||||||||||||||||||||
/// the other hand, closing the file descriptor does not unmap the | ||||||||||||||||||||||||
/// region. | ||||||||||||||||||||||||
/// - Parameters: | ||||||||||||||||||||||||
/// - memoryMap: The memory map to unmap | ||||||||||||||||||||||||
/// - length: Amount in bytes to unmap. | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public func memoryUnmap(memoryMap: UnsafeMutableRawPointer, length: Int) throws { | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this belongs under I think
Suggested change
|
||||||||||||||||||||||||
_ = try _memoryUnmap(memoryMap: memoryMap, length: length).get() | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
@usableFromInline | ||||||||||||||||||||||||
internal func _memoryUnmap(memoryMap: UnsafeMutableRawPointer, length: Int) throws -> Result<CInt, Errno> { | ||||||||||||||||||||||||
valueOrErrno(retryOnInterrupt: false) { | ||||||||||||||||||||||||
system_munmap(memoryMap, length) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
/// Flushes changes made to the in-core copy of a file that | ||||||||||||||||||||||||
/// was mapped into memory using `memoryMap` back to the filesystem. | ||||||||||||||||||||||||
/// Without use of this call, there is no guarantee that changes are | ||||||||||||||||||||||||
/// written back before `memoryUnmap` is called. To be more precise, the | ||||||||||||||||||||||||
/// part of the file that corresponds to the memory area at the start | ||||||||||||||||||||||||
/// of the map and having length of `length` is updated. | ||||||||||||||||||||||||
/// - Parameters: | ||||||||||||||||||||||||
/// - memoryMap: The memory map to sync. | ||||||||||||||||||||||||
/// - length: Length to update. | ||||||||||||||||||||||||
/// - kind: Should specify one of `MemorySyncKind.synchronous` or `MemorySyncKind.asynchronous`. | ||||||||||||||||||||||||
/// - invalidateOtherMappings: Asks to invalidate other mappings of the same file (so | ||||||||||||||||||||||||
/// that they can be updated with the fresh values just | ||||||||||||||||||||||||
/// written). | ||||||||||||||||||||||||
@_alwaysEmitIntoClient | ||||||||||||||||||||||||
public func memorySync( | ||||||||||||||||||||||||
memoryMap: UnsafeMutableRawPointer, | ||||||||||||||||||||||||
length: Int, | ||||||||||||||||||||||||
kind: MemorySyncKind, | ||||||||||||||||||||||||
invalidateOtherMappings: Bool = false | ||||||||||||||||||||||||
) throws { | ||||||||||||||||||||||||
Comment on lines
+553
to
+558
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this belongs under I don't think we should split the
Suggested change
We can also have this function take an |
||||||||||||||||||||||||
_ = try _memorySync(memoryMap: memoryMap, | ||||||||||||||||||||||||
length: length, | ||||||||||||||||||||||||
flags: invalidateOtherMappings ? kind.rawValue & _MS_INVALIDATE : kind.rawValue) | ||||||||||||||||||||||||
.get() | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
@usableFromInline | ||||||||||||||||||||||||
internal func _memorySync(memoryMap: UnsafeMutableRawPointer, length: Int, flags: CInt) throws -> Result<CInt, Errno> { | ||||||||||||||||||||||||
valueOrErrno(retryOnInterrupt: false) { | ||||||||||||||||||||||||
system_msync(memoryMap, length, flags) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
#endif | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will need some editing -- this is documenting a top-level struct, so it isn't obvious what "the mapping" or "the file" is. Additionally, bitwise or is not how these options are combined in Swift.