Merge branch 'devel' into pr_field

This commit is contained in:
ringabout
2026-07-06 20:23:33 +08:00
committed by GitHub
192 changed files with 12088 additions and 1811 deletions

View File

@@ -28,6 +28,15 @@ elif defined(netbsd):
EVFILT_PROC* = 4 ## attached to struct proc
EVFILT_SIGNAL* = 5 ## attached to struct proc
EVFILT_TIMER* = 6 ## timers (in ms)
elif defined(haiku):
const
EVFILT_READ* = -1
EVFILT_WRITE* = -2
EVFILT_AIO* = -3 ## attached to aio requests
EVFILT_VNODE* = -4 ## attached to vnodes
EVFILT_PROC* = -5 ## attached to struct proc
EVFILT_SIGNAL* = -6 ## attached to struct proc
EVFILT_TIMER* = -7 ## timers
when defined(macosx):
const
EVFILT_MACHPORT* = -8 ## Mach portsets

View File

@@ -121,6 +121,13 @@ template unCheckedInc(x) =
inc(x)
{.pop.}
template newSeqForOverwrite(T: typedesc; len: int): untyped =
## Allocates a fixed-length seq whose elements will be assigned by index.
when supportsCopyMem(T) and declared(newSeqUninit):
newSeqUninit[T](len)
else: # TODO: use `newSeqUnsafe` when that's available
newSeq[T](len)
func concat*[T](seqs: varargs[seq[T]]): seq[T] =
## Takes several sequences' items and returns them inside a new sequence.
## All sequences must be of the same type.
@@ -1105,7 +1112,7 @@ template mapIt*(s: typed, op: untyped): untyped =
evalOnceAs(s2, s, compiles((let _ = s)))
var i = 0
var result = newSeq[OutType](s2.len)
var result = newSeqForOverwrite(OutType, s2.len)
for it {.inject.} in s2:
result[i] = op
i += 1
@@ -1171,10 +1178,7 @@ template newSeqWith*(len: int, init: untyped): untyped =
assert seqRand[0] != seqRand[1]
type T = typeof(init)
let newLen = len
when supportsCopyMem(T) and declared(newSeqUninit):
var result = newSeqUninit[T](newLen)
else: # TODO: use `newSeqUnsafe` when that's available
var result = newSeq[T](newLen)
var result = newSeqForOverwrite(T, newLen)
for i in 0 ..< newLen:
result[i] = init
move(result) # refs bug #7295

View File

@@ -15,12 +15,16 @@
## It also provides some fast iterators over lines in text files (or
## other "line-like", variable length, delimited records).
const
nimUseFallBack = defined(nintendoswitch) or defined(nimMemfileFallback)
when defined(windows):
import std/winlean
when defined(nimPreviewSlimSystem):
import std/widestrs
elif defined(posix):
import std/posix
when not nimUseFallBack:
import std/posix
else:
{.error: "the memfiles module is not supported on your operating system!".}
@@ -29,45 +33,48 @@ import std/oserrors
when defined(nimPreviewSlimSystem):
import std/[syncio, assertions]
elif nimUseFallBack:
import std/syncio
from system/ansi_c import c_memchr
proc newEIO(msg: string): ref IOError =
result = (ref IOError)(msg: msg)
proc setFileSize(fh: FileHandle, newFileSize = -1, oldSize = -1): OSErrorCode =
## Set the size of open file pointed to by `fh` to `newFileSize` if != -1,
## allocating | freeing space from the file system. This routine returns the
## last OSErrorCode found rather than raising to support old rollback/clean-up
## code style. [ Should maybe move to std/osfiles. ]
result = OSErrorCode(0)
if newFileSize < 0 or newFileSize == oldSize:
return result
when defined(windows):
var sizeHigh = int32(newFileSize shr 32)
let sizeLow = int32(newFileSize and 0xffffffff)
let status = setFilePointer(Handle fh, sizeLow, addr(sizeHigh), FILE_BEGIN)
let lastErr = osLastError()
if (status == INVALID_SET_FILE_POINTER and lastErr.int32 != NO_ERROR) or
setEndOfFile(Handle fh) == 0:
result = lastErr
else:
if newFileSize > oldSize: # grow the file
var e: cint = cint(0) # posix_fallocate truncates up when needed.
when declared(posix_fallocate):
while (e = posix_fallocate(fh, 0, newFileSize); e == EINTR):
discard
if e == EINVAL or e == EOPNOTSUPP or e == ENOSYS:
# fallback arguable; Most portable BUT allows SEGV
if ftruncate(fh, newFileSize) == -1:
when not nimUseFallBack:
proc setFileSize(fh: FileHandle, newFileSize = -1, oldSize = -1): OSErrorCode =
## Set the size of open file pointed to by `fh` to `newFileSize` if != -1,
## allocating | freeing space from the file system. This routine returns the
## last OSErrorCode found rather than raising to support old rollback/clean-up
## code style. [ Should maybe move to std/osfiles. ]
result = OSErrorCode(0)
if newFileSize < 0 or newFileSize == oldSize:
return result
when defined(windows):
var sizeHigh = int32(newFileSize shr 32)
let sizeLow = int32(newFileSize and 0xffffffff)
let status = setFilePointer(Handle fh, sizeLow, addr(sizeHigh), FILE_BEGIN)
let lastErr = osLastError()
if (status == INVALID_SET_FILE_POINTER and lastErr.int32 != NO_ERROR) or
setEndOfFile(Handle fh) == 0:
result = lastErr
else:
if newFileSize > oldSize: # grow the file
var e: cint = cint(0) # posix_fallocate truncates up when needed.
when declared(posix_fallocate):
while (e = posix_fallocate(fh, 0, newFileSize); e == EINTR):
discard
if e == EINVAL or e == EOPNOTSUPP or e == ENOSYS:
# fallback arguable; Most portable BUT allows SEGV
if ftruncate(fh, newFileSize) == -1:
result = osLastError()
else:
discard
elif e != 0:
result = osLastError()
else: # shrink the file
if ftruncate(fh.cint, newFileSize) == -1:
result = osLastError()
else:
discard
elif e != 0:
result = osLastError()
else: # shrink the file
if ftruncate(fh.cint, newFileSize) == -1:
result = osLastError()
type
MemFile* = object ## represents a memory mapped file
@@ -84,6 +91,89 @@ type
else:
handle*: cint ## **Caution**: Posix specific public field.
flags: cint ## **Caution**: Platform specific private field.
when nimUseFallBack:
backing: string
path: string
readonly: bool
allowRemap: bool
when nimUseFallBack:
proc fallbackMappedSize(backingLen, mappedSize, offset: int): int =
if mappedSize < -1:
raise newEIO("mappedSize cannot be less than -1")
if offset < 0 or offset > backingLen:
raise newEIO("offset out of bounds")
if mappedSize == -1:
result = backingLen - offset
else:
result = min(mappedSize, backingLen - offset)
proc setFallbackView(m: var MemFile, mappedSize, offset: int) =
m.size = fallbackMappedSize(m.backing.len, mappedSize, offset)
if m.size > 0:
m.mem = cast[pointer](addr m.backing[offset])
else:
m.mem = nil
proc openFallbackMemFile(filename: string, mode: FileMode, mappedSize,
offset, newFileSize: int,
allowRemap: bool): MemFile =
result = MemFile(
handle: -1,
flags: 0,
path: filename,
readonly: mode == fmRead,
allowRemap: allowRemap
)
if newFileSize != -1:
result.backing = newString(newFileSize)
else:
result.backing = readFile(filename)
setFallbackView(result, mappedSize, offset)
proc mapMemFallback(m: var MemFile, mode: FileMode,
mappedSize, offset: int): pointer =
if not m.allowRemap:
raise newException(IOError,
"Cannot remap MemFile opened with allowRemap=false")
if mode != fmRead and m.readonly:
raise newEIO("cannot write to read-only mapping")
let size = fallbackMappedSize(m.backing.len, mappedSize, offset)
if size > 0:
result = cast[pointer](addr m.backing[offset])
else:
result = nil
proc flushFallback(m: var MemFile) =
if m.readonly or m.path.len == 0:
return
writeFile(m.path, m.backing)
proc resizeFallback(m: var MemFile, newFileSize: int) =
if m.readonly:
raise newException(IOError, "Cannot resize read-only MemFile")
if not m.allowRemap:
raise newException(IOError,
"Cannot resize MemFile opened with allowRemap=false")
if m.size != m.backing.len:
raise newException(IOError, "Cannot resize partial MemFile")
let oldLen = m.backing.len
m.backing.setLen(newFileSize)
for i in oldLen ..< newFileSize:
m.backing[i] = '\0'
setFallbackView(m, newFileSize, 0)
proc closeFallback(m: var MemFile) =
if not m.readonly:
flushFallback(m)
m.mem = nil
m.size = 0
m.handle = -1
m.flags = 0
m.backing = ""
m.path = ""
m.readonly = false
m.allowRemap = false
proc mapMem*(m: var MemFile, mode: FileMode = fmRead,
mappedSize = -1, offset = 0, mapFlags = cint(-1)): pointer =
@@ -94,7 +184,7 @@ proc mapMem*(m: var MemFile, mode: FileMode = fmRead,
if mode == fmAppend:
raise newEIO("The append mode is not supported.")
var readonly = mode == fmRead
let readonly = mode == fmRead
when defined(windows):
result = mapViewOfFileEx(
m.mapHandle,
@@ -105,6 +195,8 @@ proc mapMem*(m: var MemFile, mode: FileMode = fmRead,
nil)
if result == nil:
raiseOSError(osLastError())
elif nimUseFallBack:
result = mapMemFallback(m, mode, mappedSize, offset)
else:
assert mappedSize > 0
@@ -132,6 +224,8 @@ proc unmapMem*(f: var MemFile, p: pointer, size: int) =
## via `mapMem`.
when defined(windows):
if unmapViewOfFile(p) == 0: raiseOSError(osLastError())
elif nimUseFallBack:
discard
else:
if munmap(p, size) != 0: raiseOSError(osLastError())
@@ -178,7 +272,7 @@ proc open*(filename: string, mode: FileMode = fmRead,
raise newEIO("The append mode is not supported.")
assert newFileSize == -1 or mode != fmRead
var readonly = mode == fmRead
let readonly = mode == fmRead
template rollback =
result.mem = nil
@@ -252,7 +346,10 @@ proc open*(filename: string, mode: FileMode = fmRead,
if closeHandle(result.fHandle) != 0:
result.fHandle = INVALID_HANDLE_VALUE
else:
elif nimUseFallBack:
result = openFallbackMemFile(filename, mode, mappedSize, offset,
newFileSize, allowRemap)
elif defined(posix):
template fail(errCode: OSErrorCode, msg: string) =
rollback()
if result.handle != -1: discard close(result.handle)
@@ -309,6 +406,8 @@ proc flush*(f: var MemFile; attempts: Natural = 3) =
lastErr = osLastError()
if lastErr != ERROR_LOCK_VIOLATION.OSErrorCode:
raiseOSError(lastErr)
elif nimUseFallBack:
flushFallback(f)
else:
for i in 1..attempts:
res = msync(f.mem, f.size, MS_SYNC or MS_INVALIDATE) == 0
@@ -318,59 +417,71 @@ proc flush*(f: var MemFile; attempts: Natural = 3) =
if lastErr != EBUSY.OSErrorCode:
raiseOSError(lastErr, "error flushing mapping")
proc resize*(f: var MemFile, newFileSize: int) {.raises: [IOError, OSError].} =
## Resize & re-map the file underlying an `allowRemap MemFile`. If the OS/FS
## supports it, file space is reserved to ensure room for new virtual pages.
## Caller should wait often enough for `flush` to finish to limit use of
## system RAM for write buffering, perhaps just prior to this call.
## **Note**: this assumes the entire file is mapped read-write at offset 0.
## Also, the value of `.mem` will probably change.
if newFileSize < 1: # Q: include system/bitmasks & use PageSize ?
raise newException(IOError, "Cannot resize MemFile to < 1 byte")
when defined(windows):
if not f.wasOpened:
raise newException(IOError, "Cannot resize unopened MemFile")
if f.fHandle == INVALID_HANDLE_VALUE:
raise newException(IOError,
"Cannot resize MemFile opened with allowRemap=false")
if unmapViewOfFile(f.mem) == 0 or closeHandle(f.mapHandle) == 0: # Un-do map
raiseOSError(osLastError())
if newFileSize != f.size: # Seek to size & `setEndOfFile` => allocated.
if (let e = setFileSize(f.fHandle.FileHandle, newFileSize);
e != 0.OSErrorCode): raiseOSError(e)
f.mapHandle = createFileMappingW(f.fHandle, nil, PAGE_READWRITE, 0,0,nil)
if f.mapHandle == 0: # Re-do map
raiseOSError(osLastError())
let m = mapViewOfFileEx(f.mapHandle, FILE_MAP_READ or FILE_MAP_WRITE,
0, 0, WinSizeT(newFileSize), nil)
if m != nil:
f.mem = m
when nimUseFallBack:
proc resize*(f: var MemFile, newFileSize: int) {.raises: [IOError].} =
## Resize & re-map the file underlying an `allowRemap MemFile`. If the OS/FS
## supports it, file space is reserved to ensure room for new virtual pages.
## Caller should wait often enough for `flush` to finish to limit use of
## system RAM for write buffering, perhaps just prior to this call.
## **Note**: this assumes the entire file is mapped read-write at offset 0.
## Also, the value of `.mem` will probably change.
if newFileSize < 1: # Q: include system/bitmasks & use PageSize ?
raise newException(IOError, "Cannot resize MemFile to < 1 byte")
resizeFallback(f, newFileSize)
else:
proc resize*(f: var MemFile, newFileSize: int) {.raises: [IOError, OSError].} =
## Resize & re-map the file underlying an `allowRemap MemFile`. If the OS/FS
## supports it, file space is reserved to ensure room for new virtual pages.
## Caller should wait often enough for `flush` to finish to limit use of
## system RAM for write buffering, perhaps just prior to this call.
## **Note**: this assumes the entire file is mapped read-write at offset 0.
## Also, the value of `.mem` will probably change.
if newFileSize < 1: # Q: include system/bitmasks & use PageSize ?
raise newException(IOError, "Cannot resize MemFile to < 1 byte")
when defined(windows):
if not f.wasOpened:
raise newException(IOError, "Cannot resize unopened MemFile")
if f.fHandle == INVALID_HANDLE_VALUE:
raise newException(IOError,
"Cannot resize MemFile opened with allowRemap=false")
if unmapViewOfFile(f.mem) == 0 or closeHandle(f.mapHandle) == 0: # Un-do map
raiseOSError(osLastError())
if newFileSize != f.size: # Seek to size & `setEndOfFile` => allocated.
if (let e = setFileSize(f.fHandle.FileHandle, newFileSize);
e != 0.OSErrorCode): raiseOSError(e)
f.mapHandle = createFileMappingW(f.fHandle, nil, PAGE_READWRITE, 0,0,nil)
if f.mapHandle == 0: # Re-do map
raiseOSError(osLastError())
let m = mapViewOfFileEx(f.mapHandle, FILE_MAP_READ or FILE_MAP_WRITE,
0, 0, WinSizeT(newFileSize), nil)
if m != nil:
f.mem = m
f.size = newFileSize
else:
raiseOSError(osLastError())
elif defined(posix):
if f.handle == -1:
raise newException(IOError,
"Cannot resize MemFile opened with allowRemap=false")
if newFileSize != f.size:
let e = setFileSize(f.handle.FileHandle, newFileSize, f.size)
if e != 0.OSErrorCode: raiseOSError(e)
when defined(linux): #Maybe NetBSD, too?
# On Linux this can be over 100 times faster than a munmap,mmap cycle.
proc mremap(old: pointer; oldSize, newSize: csize_t; flags: cint):
pointer {.importc: "mremap", header: "<sys/mman.h>".}
let newAddr = mremap(f.mem, csize_t(f.size), csize_t(newFileSize), 1.cint)
if newAddr == cast[pointer](MAP_FAILED):
raiseOSError(osLastError())
else:
if munmap(f.mem, f.size) != 0:
raiseOSError(osLastError())
let newAddr = mmap(nil, newFileSize, PROT_READ or PROT_WRITE,
f.flags, f.handle, 0)
if newAddr == cast[pointer](MAP_FAILED):
raiseOSError(osLastError())
f.mem = newAddr
f.size = newFileSize
else:
raiseOSError(osLastError())
elif defined(posix):
if f.handle == -1:
raise newException(IOError,
"Cannot resize MemFile opened with allowRemap=false")
if newFileSize != f.size:
let e = setFileSize(f.handle.FileHandle, newFileSize, f.size)
if e != 0.OSErrorCode: raiseOSError(e)
when defined(linux): #Maybe NetBSD, too?
# On Linux this can be over 100 times faster than a munmap,mmap cycle.
proc mremap(old: pointer; oldSize, newSize: csize_t; flags: cint):
pointer {.importc: "mremap", header: "<sys/mman.h>".}
let newAddr = mremap(f.mem, csize_t(f.size), csize_t(newFileSize), 1.cint)
if newAddr == cast[pointer](MAP_FAILED):
raiseOSError(osLastError())
else:
if munmap(f.mem, f.size) != 0:
raiseOSError(osLastError())
let newAddr = mmap(nil, newFileSize, PROT_READ or PROT_WRITE,
f.flags, f.handle, 0)
if newAddr == cast[pointer](MAP_FAILED):
raiseOSError(osLastError())
f.mem = newAddr
f.size = newFileSize
proc close*(f: var MemFile) =
## closes the memory mapped file `f`. All changes are written back to the
@@ -389,6 +500,8 @@ proc close*(f: var MemFile) =
f.fHandle = INVALID_HANDLE_VALUE
if error:
lastErr = osLastError()
elif nimUseFallBack:
closeFallback(f)
else:
error = munmap(f.mem, f.size) != 0
lastErr = osLastError()

View File

@@ -11,17 +11,119 @@ when defined(nimPreviewSlimSystem):
when weirdTarget:
discard
elif defined(windows):
import std/[winlean, times]
import std/winlean
from std/strutils import toHex, toLowerAscii
const
reparseHeaderSize = 8
substituteNameOffsetField = 8
substituteNameLengthField = 10
symlinkFlagsField = 16
mountPointPathBufferOffset = 16
symlinkPathBufferOffset = 20
type
ReparseBuffer = array[MAXIMUM_REPARSE_DATA_BUFFER_SIZE, byte]
ReparseLinkInfo = object
tag: int32
flags: int32
pathBufOffset: int
flagsField: int
substituteNameOffset: int
substituteNameLength: int
template readU16(buf: ReparseBuffer; off: int): uint16 =
uint16(buf[off]) or (uint16(buf[off + 1]) shl 8)
template readI32(buf: ReparseBuffer; off: int): int32 =
cast[int32](
uint32(buf[off]) or (uint32(buf[off + 1]) shl 8) or
(uint32(buf[off + 2]) shl 16) or (uint32(buf[off + 3]) shl 24))
func startsWithAsciiIgnoreCase(wide: openArray[Utf16Char]; prefix: openArray[char]): bool =
## Matches an ASCII prefix against UTF-16 code units.
##
## This is only correct for ASCII prefixes.
## It is not a valid general case-insensitive Unicode comparison
## and must not be used for arbitrary UTF-16 text.
if prefix.len > wide.len:
return false
var i = 0
while i < prefix.len:
let rune = ord(wide[i])
if rune > 0x7F or toLowerAscii(char(rune)) != toLowerAscii(prefix[i]):
return false
inc i
true
proc decodeWinTarget(wide: openArray[Utf16Char]): string =
if wide.startsWithAsciiIgnoreCase(r"\??\unc\"):
r"\\" & $(wide.toOpenArray(8, wide.len - 1))
elif wide.startsWithAsciiIgnoreCase(r"\??\"):
$(wide.toOpenArray(4, wide.len - 1))
else:
$wide
template invalidReparseData(path, details: string) =
raise newException(OSError,
"expandSymlink: invalid reparse data for " & path & " (" & details & ")")
proc parseReparseLinkInfo(buf: ReparseBuffer; bytesReturned: int;
symlinkPath: string): ReparseLinkInfo =
if bytesReturned < reparseHeaderSize:
invalidReparseData(symlinkPath, "truncated header")
let
reparseDataLen = int(readU16(buf, 4))
wholeDataLen = reparseHeaderSize + reparseDataLen
if wholeDataLen > bytesReturned:
invalidReparseData(symlinkPath, "payload exceeds returned size")
result.tag = readI32(buf, 0)
case result.tag
of IO_REPARSE_TAG_SYMLINK:
result.pathBufOffset = symlinkPathBufferOffset
result.flagsField = symlinkFlagsField
of IO_REPARSE_TAG_MOUNT_POINT:
result.pathBufOffset = mountPointPathBufferOffset
result.flagsField = -1
else:
raise newException(OSError,
"expandSymlink: unsupported reparse tag for " & symlinkPath &
" (ReparseTag=0x" & toHex(result.tag) & ")")
if result.pathBufOffset > wholeDataLen:
invalidReparseData(symlinkPath, "missing path buffer")
result.substituteNameOffset = int(readU16(buf, substituteNameOffsetField))
result.substituteNameLength = int(readU16(buf, substituteNameLengthField))
if result.substituteNameLength <= 0:
invalidReparseData(symlinkPath, "empty substitute name")
if (result.substituteNameOffset and 1) != 0 or
(result.substituteNameLength and 1) != 0:
invalidReparseData(symlinkPath, "unaligned UTF-16 substitute name")
let startByte = result.pathBufOffset + result.substituteNameOffset
let endByte = startByte + result.substituteNameLength
if startByte < result.pathBufOffset or endByte < startByte or
endByte > wholeDataLen:
invalidReparseData(symlinkPath, "substitute name out of bounds")
result.flags =
if result.flagsField >= 0:
readI32(buf, result.flagsField)
else:
0
elif defined(posix):
import std/posix
when weirdTarget:
{.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".}
else:
{.pragma: noWeirdTarget.}
when defined(nimscript):
# for procs already defined in scriptconfig.nim
template noNimJs(body): untyped = discard
@@ -56,13 +158,65 @@ proc createSymlink*(src, dest: string) {.noWeirdTarget.} =
raiseOSError(osLastError(), $(src, dest))
proc expandSymlink*(symlinkPath: string): string {.noWeirdTarget.} =
## Returns a string representing the path to which the symbolic link points.
## Returns the stored target of the symbolic link `symlinkPath`.
##
## On Windows this is a noop, `symlinkPath` is simply returned.
## This expands exactly one level of indirection, like POSIX `readlink`.
## If the target is itself a symbolic link, it is returned as-is rather than
## being expanded further.
##
## On POSIX, raises `OSError` if `symlinkPath` is not a symbolic link or if
## the target cannot be read.
##
## On Windows, this supports symbolic links and junctions by reading the
## reparse point payload directly. Unsupported reparse tags raise `OSError`.
##
## On Nintendo Switch this is currently a noop: `symlinkPath` is simply
## returned, without checking whether it is actually a symbolic link.
##
## See also:
## * `createSymlink proc`_
when defined(windows) or defined(nintendoswitch):
when defined(windows):
let handle = createFileW(
newWideCString(symlinkPath),
0'i32,
FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
nil,
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT or FILE_FLAG_BACKUP_SEMANTICS,
Handle(0)
)
if handle == INVALID_HANDLE_VALUE:
raiseOSError(osLastError(), "expandSymlink: cannot open " & symlinkPath)
defer:
discard closeHandle(handle)
var buf: ReparseBuffer
var bytesReturned: DWORD
if deviceIoControl(
handle,
FSCTL_GET_REPARSE_POINT,
nil, 0'i32,
addr buf[0], DWORD(buf.len),
bytesReturned,
nil
) == 0:
raiseOSError(osLastError(),
"expandSymlink: DeviceIoControl failed for " & symlinkPath)
let
info = parseReparseLinkInfo(buf, int(bytesReturned), symlinkPath)
startByte = info.pathBufOffset + info.substituteNameOffset
runeLen = info.substituteNameLength shr 1
wideSlicePtr = cast[ptr UncheckedArray[Utf16Char]](addr buf[startByte])
if info.tag == IO_REPARSE_TAG_SYMLINK and
(info.flags and SYMLINK_FLAG_RELATIVE) != 0:
return $(wideSlicePtr.toOpenArray(0, runeLen - 1))
decodeWinTarget(wideSlicePtr.toOpenArray(0, runeLen - 1))
elif defined(nintendoswitch):
result = symlinkPath
else:
var bufLen = 1024

View File

@@ -24,9 +24,20 @@ proc createSymlink*(src, dest: Path) {.inline.} =
createSymlink(src.string, dest.string)
proc expandSymlink*(symlinkPath: Path): Path {.inline.} =
## Returns a string representing the path to which the symbolic link points.
## Returns the stored target of the symbolic link `symlinkPath`.
##
## On Windows this is a noop, `symlinkPath` is simply returned.
## This expands exactly one level of indirection, like POSIX `readlink`.
## If the target is itself a symbolic link, it is returned as-is rather than
## being expanded further.
##
## On POSIX, raises `OSError` if `symlinkPath` is not a symbolic link or if
## the target cannot be read.
##
## On Windows, this supports symbolic links and junctions by reading the
## reparse point payload directly. Unsupported reparse tags raise `OSError`.
##
## On Nintendo Switch this is currently a noop: `symlinkPath` is simply
## returned, without checking whether it is actually a symbolic link.
##
## See also:
## * `createSymlink proc`_

View File

@@ -185,47 +185,88 @@ when not (defined(cpu16) or defined(cpu8)):
proc newWideCString*(s: string): WideCStringObj =
result = newWideCString(cstring s, s.len)
proc `$`*(w: WideCString, estimate: int, replacement: int = 0xFFFD): string =
result = newStringOfCap(estimate + estimate shr 2)
iterator decodeUtf16(w: WideCString; replacement: int): int =
## Looks for a terminating NUL for length
var i = 0
while w[i].int16 != 0'i16:
var ch = ord(w[i])
inc i
if ch >= UNI_SUR_HIGH_START and ch <= UNI_SUR_HIGH_END:
# If the 16 bits following the high surrogate are in the source buffer...
let ch2 = ord(w[i])
# If it's a low surrogate, convert to UTF32:
if ch2 >= UNI_SUR_LOW_START and ch2 <= UNI_SUR_LOW_END:
ch = (((ch and halfMask) shl halfShift) + (ch2 and halfMask)) + halfBase
inc i
# If the 16 bits following the high surrogate are NOT in the source...
if w[i].int16 == 0'i16:
ch = replacement #invalid UTF-16
else:
#invalid UTF-16
ch = replacement
let ch2 = ord(w[i])
# If it's a low surrogate, convert to UTF32:
if ch2 >= UNI_SUR_LOW_START and ch2 <= UNI_SUR_LOW_END:
ch = (((ch and halfMask) shl halfShift) + (ch2 and halfMask)) + halfBase
inc i
else:
ch = replacement #invalid UTF-16
elif ch >= UNI_SUR_LOW_START and ch <= UNI_SUR_LOW_END:
#invalid UTF-16
ch = replacement
ch = replacement #invalid UTF-16
yield ch
if ch < 0x80:
result.add chr(ch)
elif ch < 0x800:
result.add chr((ch shr 6) or 0xc0)
result.add chr((ch and 0x3f) or 0x80)
elif ch < 0x10000:
result.add chr((ch shr 12) or 0xe0)
result.add chr(((ch shr 6) and 0x3f) or 0x80)
result.add chr((ch and 0x3f) or 0x80)
elif ch <= 0x10FFFF:
result.add chr((ch shr 18) or 0xf0)
result.add chr(((ch shr 12) and 0x3f) or 0x80)
result.add chr(((ch shr 6) and 0x3f) or 0x80)
result.add chr((ch and 0x3f) or 0x80)
else:
# replacement char(in case user give very large number):
result.add chr(0xFFFD shr 12 or 0b1110_0000)
result.add chr(0xFFFD shr 6 and ones(6) or 0b10_0000_00)
result.add chr(0xFFFD and ones(6) or 0b10_0000_00)
iterator decodeUtf16(w: openArray[Utf16Char]; replacement: int): int =
## Doesn't look for terminating NUL for length, trusts `w.len`
var i = 0
while i < w.len:
var ch = ord(w[i])
inc i
if ch >= UNI_SUR_HIGH_START and ch <= UNI_SUR_HIGH_END:
# If the 16 bits following the high surrogate are NOT in the source...
if i >= w.len:
ch = replacement #invalid UTF-16
else:
let ch2 = ord(w[i])
# If it's a low surrogate, convert to UTF32:
if ch2 >= UNI_SUR_LOW_START and ch2 <= UNI_SUR_LOW_END:
ch = (((ch and halfMask) shl halfShift) + (ch2 and halfMask)) + halfBase
inc i
else:
ch = replacement #invalid UTF-16
elif ch >= UNI_SUR_LOW_START and ch <= UNI_SUR_LOW_END:
ch = replacement #invalid UTF-16
yield ch
proc addUtf8(dest: var string; rune: int) =
if rune < 0x80:
dest.add chr(rune)
elif rune < 0x800:
dest.add chr((rune shr 6) or 0xc0)
dest.add chr((rune and 0x3f) or 0x80)
elif rune < 0x10000:
dest.add chr((rune shr 12) or 0xe0)
dest.add chr(((rune shr 6) and 0x3f) or 0x80)
dest.add chr((rune and 0x3f) or 0x80)
elif rune <= 0x10FFFF:
dest.add chr((rune shr 18) or 0xf0)
dest.add chr(((rune shr 12) and 0x3f) or 0x80)
dest.add chr(((rune shr 6) and 0x3f) or 0x80)
dest.add chr((rune and 0x3f) or 0x80)
else:
# replacement char (in case user give very large number):
dest.add chr(0xFFFD shr 12 or 0b1110_0000)
dest.add chr(0xFFFD shr 6 and ones(6) or 0b10_0000_00)
dest.add chr(0xFFFD and ones(6) or 0b10_0000_00)
proc `$`*(w: openArray[Utf16Char]; replacement: int = 0xFFFD): string =
## Decodes a length-delimited UTF-16 slice to UTF-8.
##
## Unlike the `WideCString` overloads, this preserves the provided length
## and does not search for a terminating NUL.
if w.len == 0:
result = ""
else:
result = newStringOfCap(w.len + w.len shr 2)
for rune in w.decodeUtf16(replacement):
result.addUtf8(rune)
proc `$`*(w: WideCString; estimate: int; replacement: int = 0xFFFD): string =
result = newStringOfCap(estimate + estimate shr 2)
for rune in w.decodeUtf16(replacement):
result.addUtf8(rune)
proc `$`*(s: WideCString): string =
result = s $ 80

View File

@@ -54,7 +54,12 @@ type
typeOfProc, ## Prefer the interpretation that means `x` is a proc call.
typeOfIter ## Prefer the interpretation that means `x` is an iterator call.
proc typeof*(x: untyped; mode = typeOfIter): typedesc {.
TypeOfModifiers* = enum ## Modes to handle type modifiers `var`, `sink` and `lent`.
CompatibleTypeModifiers, ## Remove or keep type modifiers in the same way as old typeof. That means keep `sink` but remove `var` and `lent`.
RemoveTypeModifiers, ## Remove type modifiers.
KeepTypeModifiers, ## Keep type modifiers.
proc typeof*(x: untyped; mode = typeOfIter; modifierMode = CompatibleTypeModifiers): typedesc {.
magic: "TypeOf", noSideEffect, compileTime.} =
## Builtin `typeof` operation for accessing the type of an expression.
## Since version 0.20.0.
@@ -76,6 +81,11 @@ proc typeof*(x: untyped; mode = typeOfIter): typedesc {.
# since `typeOfProc` expects a typed expression and `myFoo2()` can
# only be used in a `for` context.
proc varParam(x: var int;
y: typeof(x, modifierMode = RemoveTypeModifiers);
z: typeof(x, modifierMode = KeepTypeModifiers)) = discard
doAssert varParam is proc (x: var int; y: int; z: var int) {.nimcall.}
proc `or`*(a, b: typedesc): typedesc {.magic: "TypeTrait", noSideEffect.}
## Constructs an `or` meta class.
@@ -1713,11 +1723,18 @@ when not (notJSnotNims and defined(nimSeqsV2)):
let ns = cast[NimString](s)
if ns == nil: nil
else: cast[ptr UncheckedArray[char]](addr ns.data[start])
template readRawDataStable*(s: var string; start = 0): ptr UncheckedArray[char] =
## Same as `readRawData` here: the data lives in a heap `NimStringDesc` at a
## stable address, so the pointer already survives moves of `s`. Takes `s` by
## `var` to match the `--strings:sso` version, so code can prepare for that
## upgrade without `when declared` guards.
readRawData(s, start)
else:
# JS/nimscript: callers are guarded by whenNotVmJsNims/when not defined(js)
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} = nil
proc endStore*(s: var string) {.inline, noSideEffect, raises: [], tags: [].} = discard
template readRawData*(s: string; start = 0): ptr UncheckedArray[char] = nil
template readRawDataStable*(s: var string; start = 0): ptr UncheckedArray[char] = nil
when not defined(js):
template newSeqImpl(T, len) =
@@ -3121,10 +3138,7 @@ when notJSnotNims:
not defined(nuttx) and
hostOS != "any"
proc raiseEIO(msg: string) {.noinline, noreturn.} =
raise newException(IOError, msg)
proc echoBinSafe(args: openArray[string]) {.compilerproc.} =
proc echoBinSafe(args: openArray[string]) {.compilerproc, raises: [].} =
when defined(androidNDK):
# When running nim in android app, stdout goes nowhere, so echo gets ignored
# To redirect echo to the android logcat, use -d:androidNDK
@@ -3146,7 +3160,7 @@ when notJSnotNims:
for s in args:
when defined(windows):
# equivalent to syncio.writeWindows
proc writeWindows(f: CFilePtr; s: string; doRaise = false) =
proc writeWindows(f: CFilePtr; s: string) =
# Don't ask why but the 'printf' family of function is the only thing
# that writes utf-8 strings reliably on Windows. At least on my Win 10
# machine. We also enable `setConsoleOutputCP(65001)` now by default.
@@ -3157,13 +3171,11 @@ when notJSnotNims:
if s[i] == '\0':
let w = c_fputc('\0', f)
if w != 0:
if doRaise: raiseEIO("cannot write string to file")
break
inc i
else:
let w = c_fprintf(f, "%s", unsafeAddr s[i])
if w <= 0:
if doRaise: raiseEIO("cannot write string to file")
break
inc i, w
writeWindows(cstdout, s)

View File

@@ -1,13 +1,13 @@
when notJSnotNims:
proc zeroMem*(p: pointer, size: Natural) {.inline, noSideEffect,
tags: [], raises: [], enforceNoRaises.}
proc zeroMem*(p: pointer, size: Natural) {.inline, gcsafe,
tags: [], raises: [], enforceNoRaises, noSideEffect.}
## Overwrites the contents of the memory at `p` with the value 0.
##
## Exactly `size` bytes will be overwritten. Like any procedure
## dealing with raw memory this is **unsafe**.
proc copyMem*(dest, source: pointer, size: Natural) {.inline, gcsafe,
tags: [], raises: [], enforceNoRaises.}
tags: [], raises: [], enforceNoRaises, noSideEffect.}
## Copies the contents from the memory at `source` to the memory
## at `dest`.
## Exactly `size` bytes will be copied. The memory
@@ -15,7 +15,7 @@ when notJSnotNims:
## memory this is **unsafe**.
proc moveMem*(dest, source: pointer, size: Natural) {.inline, gcsafe,
tags: [], raises: [], enforceNoRaises.}
tags: [], raises: [], enforceNoRaises, noSideEffect.}
## Copies the contents from the memory at `source` to the memory
## at `dest`.
##
@@ -24,8 +24,8 @@ when notJSnotNims:
## and is thus somewhat more safe than `copyMem`. Like any procedure
## dealing with raw memory this is still **unsafe**, though.
proc equalMem*(a, b: pointer, size: Natural): bool {.inline, noSideEffect,
tags: [], raises: [], enforceNoRaises.}
proc equalMem*(a, b: pointer, size: Natural): bool {.inline, gcsafe,
tags: [], raises: [], enforceNoRaises, noSideEffect.}
## Compares the memory blocks `a` and `b`. `size` bytes will
## be compared.
##
@@ -33,8 +33,8 @@ when notJSnotNims:
## otherwise. Like any procedure dealing with raw memory this is
## **unsafe**.
proc cmpMem*(a, b: pointer, size: Natural): int {.inline, noSideEffect,
tags: [], raises: [], enforceNoRaises.}
proc cmpMem*(a, b: pointer, size: Natural): int {.inline, gcsafe,
tags: [], raises: [], enforceNoRaises, noSideEffect.}
## Compares the memory blocks `a` and `b`. `size` bytes will
## be compared.
##

View File

@@ -91,7 +91,10 @@ else:
elif defined(gcMarkAndSweep):
# XXX use 'compileOption' here
include "system/gc_ms"
else:
elif not (defined(nimV2) or usesDestructors):
# equivalent to a plain `else` here, but spelled out so that the IC
# dependency scanner (which sees `else` imports/includes unguarded)
# doesn't schedule system/gc's transitive imports under --mm:orc
include "system/gc"
when not declared(nimNewSeqOfCap) and not defined(nimSeqsV2):

View File

@@ -89,7 +89,7 @@ elif defined(emscripten) and not defined(StandaloneHeapSize):
var mmapDescrPos = cast[int](result) -% sizeof(EmscriptenMMapBlock)
var mmapDescr = cast[EmscriptenMMapBlock](mmapDescrPos)
var mmapDescr = cast[PEmscriptenMMapBlock](mmapDescrPos)
mmapDescr.realSize = realSize
mmapDescr.realPointer = realPointer
@@ -99,7 +99,7 @@ elif defined(emscripten) and not defined(StandaloneHeapSize):
proc osDeallocPages(p: pointer, size: int) {.inline.} =
var mmapDescrPos = cast[int](p) -% sizeof(EmscriptenMMapBlock)
var mmapDescr = cast[EmscriptenMMapBlock](mmapDescrPos)
var mmapDescr = cast[PEmscriptenMMapBlock](mmapDescrPos)
munmap(mmapDescr.realPointer, mmapDescr.realSize)
elif defined(genode) and not defined(StandaloneHeapSize):

View File

@@ -40,7 +40,8 @@ type
wasm32, ## WASM, 32-bit
e2k, ## MCST Elbrus 2000
loongarch64, ## LoongArch 64-bit processor
s390x ## IBM Z
s390x, ## IBM Z
wasm64 ## WASM, 64-bit
OsPlatform* {.pure.} = enum ## the OS this program will run on.
none, dos, windows, os2, linux, morphos, skyos, solaris,
@@ -101,5 +102,6 @@ const
elif defined(e2k): CpuPlatform.e2k
elif defined(loongarch64): CpuPlatform.loongarch64
elif defined(s390x): CpuPlatform.s390x
elif defined(wasm64): CpuPlatform.wasm64
else: CpuPlatform.none
## the CPU this program will run on.

View File

@@ -261,4 +261,14 @@ template readRawData*(s: string; start = 0): ptr UncheckedArray[char] =
## Template ensures no copy of `s`; ptr is valid while `s` is alive.
rawDataImpl(cast[ptr NimStringV2](unsafeAddr s), start)
template readRawDataStable*(s: var string; start = 0): ptr UncheckedArray[char] =
## Like `readRawData`, but the returned pointer additionally survives moves and
## copies of `s` (while `s` stays alive and is not reassigned). For this string
## implementation the char data already lives in a heap payload at an address
## independent of the `string` value itself, so no promotion is needed and this
## is identical to `readRawData`. Takes `s` by `var` to match the `--strings:sso`
## version (which promotes a small inline string to the heap), so code written
## against `readRawDataStable` compiles unchanged under either implementation.
rawDataImpl(cast[ptr NimStringV2](addr s), start)
{.pop.}

View File

@@ -770,6 +770,33 @@ template readRawData*(s: string; start = 0): ptr UncheckedArray[char] =
## Template ensures no copy of `s` is made; ptr is valid while `s` is alive.
rawDataImpl(cast[ptr SmallString](unsafeAddr s), start)
proc readRawDataStable*(s: var string; start = 0): ptr UncheckedArray[char] {.inline.} =
## Like `readRawData`, but the returned pointer stays valid across moves and
## copies of `s` (as long as `s` stays alive and is not reassigned). A
## short/medium string keeps its chars *inline* in the string object, so a
## plain `readRawData` pointer dangles the moment the object is moved; this
## promotes `s` to its heap (long) representation first, whose payload address
## is independent of where the string object itself lives. Use this whenever an
## interior pointer must outlive the current scope of the owning string (e.g.
## a cursor cached alongside the buffer it points into).
let ss = cast[ptr SmallString](addr s)
let slen = ssLen(ss[])
if slen > 0 and slen <= PayloadSize:
# Promote inline/medium to a long heap block so the payload lives at a
# stable address. Mirrors the short/medium -> long transition in `add`.
let newCap = max(slen, resize(slen))
let p = cast[ptr LongString](alloc(LongStringDataOffset + newCap + 1))
p.rc = 1
p.fullLen = slen
p.capImpl = newCap
copyMem(addr p.data[0], inlinePtr(ss[]), slen)
p.data[slen] = '\0'
ss[].more = p
setSSLen(ss[], HeapSlen)
# Hot-prefix cache (bytes 1..AlwaysAvail) already mirrors data[0..AlwaysAvail-1]
# because setSSLen only rewrote byte 0; the inline chars are untouched.
rawDataImpl(ss, start)
# These take `string` (tyString) so the codegen uses them directly, bypassing
# strmantle.nim's versions which go through nimStrLen/nimStrAtMutV3 compilerproc calls.
proc cmpStrings(a, b: string): int {.compilerproc, inline.} =

View File

@@ -261,6 +261,11 @@ const
FILE_ATTRIBUTE_OFFLINE* = 0x00001000'i32
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED* = 0x00002000'i32
IO_REPARSE_TAG_MOUNT_POINT* = 0xA0000003'i32
IO_REPARSE_TAG_SYMLINK* = 0xA000000C'i32
MAXIMUM_REPARSE_DATA_BUFFER_SIZE* = 16 * 1024
SYMLINK_FLAG_RELATIVE* = 0x1'i32
FILE_FLAG_FIRST_PIPE_INSTANCE* = 0x00080000'i32
FILE_FLAG_OPEN_NO_RECALL* = 0x00100000'i32
FILE_FLAG_OPEN_REPARSE_POINT* = 0x00200000'i32
@@ -282,6 +287,10 @@ const
MOVEFILE_REPLACE_EXISTING* = 0x1'i32
MOVEFILE_WRITE_THROUGH* = 0x8'i32
# CTL_CODE(FILE_DEVICE_FILE_SYSTEM = 9, func = 42, METHOD_BUFFERED = 0,
# FILE_ANY_ACCESS = 0)
FSCTL_GET_REPARSE_POINT* = 0x000900A8'i32
type
WIN32_FIND_DATA* {.pure.} = object
dwFileAttributes*: int32
@@ -654,6 +663,12 @@ proc createFileW*(lpFileName: WideCString, dwDesiredAccess, dwShareMode: DWORD,
dwCreationDisposition, dwFlagsAndAttributes: DWORD,
hTemplateFile: Handle): Handle {.
stdcall, dynlib: "kernel32", importc: "CreateFileW".}
proc deviceIoControl*(hDevice: Handle, dwIoControlCode: DWORD,
lpInBuffer: pointer, nInBufferSize: DWORD,
lpOutBuffer: pointer, nOutBufferSize: DWORD,
lpBytesReturned: var DWORD,
lpOverlapped: pointer): WINBOOL {.
stdcall, dynlib: "kernel32", importc: "DeviceIoControl".}
proc deleteFileW*(pathName: WideCString): int32 {.
importc: "DeleteFileW", dynlib: "kernel32", stdcall.}
proc createFileA*(lpFileName: cstring, dwDesiredAccess, dwShareMode: DWORD,