Merge branch 'devel' into pr_remove_macros

This commit is contained in:
ringabout
2024-08-14 15:17:53 +08:00
committed by GitHub
251 changed files with 6721 additions and 10708 deletions

View File

@@ -93,6 +93,8 @@ type
nnkFuncDef,
nnkTupleConstr,
nnkError, ## erroneous AST node
nnkModuleRef, nnkReplayAction, nnkNilRodNode ## internal IC nodes
nnkOpenSym
NimNodeKinds* = set[NimNodeKind]
NimTypeKind* = enum # some types are no longer used, see ast.nim
@@ -227,7 +229,7 @@ when (NimMajor, NimMinor, NimPatch) >= (1, 3, 5) or defined(nimSymImplTransform)
## note that code transformations are implementation dependent and subject to change.
## See an example in `tests/macros/tmacros_various.nim`.
proc owner*(sym: NimNode): NimNode {.magic: "SymOwner", noSideEffect.}
proc owner*(sym: NimNode): NimNode {.magic: "SymOwner", noSideEffect, deprecated.}
## Accepts a node of kind `nnkSym` and returns its owner's symbol.
## The meaning of 'owner' depends on `sym`'s `NimSymKind` and declaration
## context. For top level declarations this is an `nskModule` symbol,
@@ -1329,7 +1331,7 @@ proc `$`*(node: NimNode): string =
result = node.basename.strVal & "*"
of nnkStrLit..nnkTripleStrLit, nnkCommentStmt, nnkSym, nnkIdent:
result = node.strVal
of nnkOpenSymChoice, nnkClosedSymChoice:
of nnkOpenSymChoice, nnkClosedSymChoice, nnkOpenSym:
result = $node[0]
of nnkAccQuoted:
result = ""

View File

@@ -129,7 +129,9 @@ when not defined(gcDestructors):
else:
proc nimNewObj(size, align: int): pointer {.importCompilerProc.}
proc newSeqPayload(cap, elemSize, elemAlign: int): pointer {.importCompilerProc.}
proc prepareSeqAdd(len: int; p: pointer; addlen, elemSize, elemAlign: int): pointer {.
proc prepareSeqAddUninit(len: int; p: pointer; addlen, elemSize, elemAlign: int): pointer {.
importCompilerProc.}
proc zeroNewElements(len: int; p: pointer; addlen, elemSize, elemAlign: int) {.
importCompilerProc.}
template `+!!`(a, b): untyped = cast[pointer](cast[int](a) + b)
@@ -221,7 +223,8 @@ proc extendSeq*(x: Any) =
var s = cast[ptr NimSeqV2Reimpl](x.value)
let elem = x.rawType.base
if s.p == nil or s.p.cap < s.len+1:
s.p = cast[ptr NimSeqPayloadReimpl](prepareSeqAdd(s.len, s.p, 1, elem.size, elem.align))
s.p = cast[ptr NimSeqPayloadReimpl](prepareSeqAddUninit(s.len, s.p, 1, elem.size, elem.align))
zeroNewElements(s.len, s.p, 1, elem.size, elem.align)
inc s.len
else:
var y = cast[ptr PGenSeq](x.value)[]

View File

@@ -243,7 +243,7 @@ since (1, 5, 1):
else:
type A = impl(onSuccess(default(T)))
var ret: A
{.emit: "`ret` = `future`.then(`onSuccess`, `onReject`)".}
{.emit: "`ret` = `future`.then(`onSuccess`, `onReject`);".}
return ret
proc catch*[T](future: Future[T], onReject: OnReject): Future[void] =
@@ -266,4 +266,4 @@ since (1, 5, 1):
discard main()
{.emit: "`result` = `future`.catch(`onReject`)".}
{.emit: "`result` = `future`.catch(`onReject`);".}

View File

@@ -14,6 +14,7 @@ __GNUC__
__TINYC__
__clang__
__AVR__
__arm__
__EMSCRIPTEN__
*/
@@ -272,11 +273,15 @@ __EMSCRIPTEN__
#elif defined(__cplusplus)
#define NIM_STATIC_ASSERT(x, msg) static_assert((x), msg)
#else
#define NIM_STATIC_ASSERT(x, msg) typedef int NIM_STATIC_ASSERT_AUX[(x) ? 1 : -1];
#define _NIM_STATIC_ASSERT_FINAL(x, append_name) typedef int NIM_STATIC_ASSERT_AUX ## append_name[(x) ? 1 : -1];
#define _NIM_STATIC_ASSERT_STAGE_3(x, line) _NIM_STATIC_ASSERT_FINAL(x, _AT_LINE_##line)
#define _NIM_STATIC_ASSERT_STAGE_2(x, line) _NIM_STATIC_ASSERT_STAGE_3(x, line)
#define NIM_STATIC_ASSERT(x, msg) _NIM_STATIC_ASSERT_STAGE_2(x,__LINE__)
// On failure, your C compiler will say something like:
// "error: 'NIM_STATIC_ASSERT_AUX' declared as an array with a negative size"
// we could use a better fallback to also show line number, using:
// http://www.pixelbeat.org/programming/gcc/static_assert.html
// "error: 'NIM_STATIC_ASSERT_AUX_AT_LINE_XXX' declared as an array with a negative size"
// Adding the line number helps to avoid redefinitions which are not allowed in
// old GCC versions, however the order of evaluation for __LINE__ is a little tricky,
// hence all the helper macros. See https://stackoverflow.com/a/3385694 for more info.
#endif
/* C99 compiler? */
@@ -473,7 +478,7 @@ typedef char* NCSTRING;
/* declared size of a sequence/variable length array: */
#if defined(__cplusplus) && defined(__clang__)
# define SEQ_DECL_SIZE 1
#elif defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER)
#elif defined(__GNUC__) || defined(_MSC_VER)
# define SEQ_DECL_SIZE /* empty is correct! */
#else
# define SEQ_DECL_SIZE 1000000
@@ -580,9 +585,16 @@ NIM_STATIC_ASSERT(sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8, "P
#define nimMulInt64(a, b, res) __builtin_smulll_overflow(a, b, (long long int*)res)
#if NIM_INTBITS == 32
#define nimAddInt(a, b, res) __builtin_sadd_overflow(a, b, res)
#define nimSubInt(a, b, res) __builtin_ssub_overflow(a, b, res)
#define nimMulInt(a, b, res) __builtin_smul_overflow(a, b, res)
#if defined(__arm__) && defined(__GNUC__)
/* arm-none-eabi-gcc targets defines int32_t as long int */
#define nimAddInt(a, b, res) __builtin_saddl_overflow(a, b, res)
#define nimSubInt(a, b, res) __builtin_ssubl_overflow(a, b, res)
#define nimMulInt(a, b, res) __builtin_smull_overflow(a, b, res)
#else
#define nimAddInt(a, b, res) __builtin_sadd_overflow(a, b, res)
#define nimSubInt(a, b, res) __builtin_ssub_overflow(a, b, res)
#define nimMulInt(a, b, res) __builtin_smul_overflow(a, b, res)
#endif
#else
/* map it to the 'long long' variant */
#define nimAddInt(a, b, res) __builtin_saddll_overflow(a, b, (long long int*)res)

View File

@@ -15,9 +15,6 @@
runnableExamples:
from std/math import almostEqual, sqrt
func almostEqual(a, b: Complex): bool =
almostEqual(a.re, b.re) and almostEqual(a.im, b.im)
let
z1 = complex(1.0, 2.0)
z2 = complex(3.0, -4.0)
@@ -412,6 +409,24 @@ func rect*[T](r, phi: T): Complex[T] =
## * `polar func<#polar,Complex[T]>`_ for the inverse operation
complex(r * cos(phi), r * sin(phi))
func almostEqual*[T: SomeFloat](x, y: Complex[T]; unitsInLastPlace: Natural = 4): bool =
## Checks if two complex values are almost equal, using the
## [machine epsilon](https://en.wikipedia.org/wiki/Machine_epsilon).
##
## Two complex values are considered almost equal if their real and imaginary
## components are almost equal.
##
## `unitsInLastPlace` is the max number of
## [units in the last place](https://en.wikipedia.org/wiki/Unit_in_the_last_place)
## difference tolerated when comparing two numbers. The larger the value, the
## more error is allowed. A `0` value means that two numbers must be exactly the
## same to be considered equal.
##
## The machine epsilon has to be scaled to the magnitude of the values used
## and multiplied by the desired precision in ULPs unless the difference is
## subnormal.
almostEqual(x.re, y.re, unitsInLastPlace = unitsInLastPlace) and
almostEqual(x.im, y.im, unitsInLastPlace = unitsInLastPlace)
func `$`*(z: Complex): string =
## Returns `z`'s string representation as `"(re, im)"`.

View File

@@ -15,75 +15,96 @@ runnableExamples:
include "system/inclrtl"
when defined(posix) and not (defined(macosx) or defined(bsd)):
import std/posix
when defined(js):
import std/jsffi
proc countProcessorsImpl(): int =
when defined(nodejs):
let jsOs = require("os")
let jsObj = jsOs.cpus().length
else:
# `navigator.hardwareConcurrency`
# works on browser as well as deno.
let navigator{.importcpp.}: JsObject
let jsObj = navigator.hardwareConcurrency
result = jsObj.to int
else:
when defined(posix) and not (defined(macosx) or defined(bsd)):
import std/posix
when defined(windows):
import std/private/win_getsysteminfo
when defined(windows):
import std/private/win_getsysteminfo
when defined(freebsd) or defined(macosx):
{.emit: "#include <sys/types.h>".}
when defined(freebsd) or defined(macosx):
{.emit: "#include <sys/types.h>".}
when defined(openbsd) or defined(netbsd):
{.emit: "#include <sys/param.h>".}
when defined(openbsd) or defined(netbsd):
{.emit: "#include <sys/param.h>".}
when defined(macosx) or defined(bsd):
# we HAVE to emit param.h before sysctl.h so we cannot use .header here
# either. The amount of archaic bullshit in Poonix based OSes is just insane.
{.emit: "#include <sys/sysctl.h>".}
const
CTL_HW = 6
HW_AVAILCPU = 25
HW_NCPU = 3
proc sysctl(x: ptr array[0..3, cint], y: cint, z: pointer,
a: var csize_t, b: pointer, c: csize_t): cint {.
importc: "sysctl", nodecl.}
when defined(macosx) or defined(bsd):
# we HAVE to emit param.h before sysctl.h so we cannot use .header here
# either. The amount of archaic bullshit in Poonix based OSes is just insane.
{.emit: "#include <sys/sysctl.h>".}
{.push nodecl.}
when defined(macosx):
proc sysctlbyname(name: cstring,
oldp: pointer, oldlenp: var csize_t,
newp: pointer, newlen: csize_t): cint {.importc.}
let
CTL_HW{.importc.}: cint
HW_NCPU{.importc.}: cint
proc sysctl[I: static[int]](name: var array[I, cint], namelen: cuint,
oldp: pointer, oldlenp: var csize_t,
newp: pointer, newlen: csize_t): cint {.importc.}
{.pop.}
when defined(genode):
import genode/env
when defined(genode):
import genode/env
proc affinitySpaceTotal(env: GenodeEnvPtr): cuint {.
importcpp: "@->cpu().affinity_space().total()".}
proc affinitySpaceTotal(env: GenodeEnvPtr): cuint {.
importcpp: "@->cpu().affinity_space().total()".}
when defined(haiku):
type
SystemInfo {.importc: "system_info", header: "<OS.h>".} = object
cpuCount {.importc: "cpu_count".}: uint32
proc getSystemInfo(info: ptr SystemInfo): int32 {.importc: "get_system_info",
header: "<OS.h>".}
proc countProcessorsImpl(): int {.inline.} =
when defined(windows):
var
si: SystemInfo
getSystemInfo(addr si)
result = int(si.dwNumberOfProcessors)
elif defined(macosx) or defined(bsd):
let dest = addr result
var len = sizeof(result).csize_t
when defined(macosx):
# alias of "hw.activecpu"
if sysctlbyname("hw.logicalcpu", dest, len, nil, 0) == 0:
return
var mib = [CTL_HW, HW_NCPU]
if sysctl(mib, 2, dest, len, nil, 0) == 0:
return
elif defined(hpux):
result = mpctl(MPC_GETNUMSPUS, nil, nil)
elif defined(irix):
var SC_NPROC_ONLN {.importc: "_SC_NPROC_ONLN", header: "<unistd.h>".}: cint
result = sysconf(SC_NPROC_ONLN)
elif defined(genode):
result = runtimeEnv.affinitySpaceTotal().int
elif defined(haiku):
var sysinfo: SystemInfo
if getSystemInfo(addr sysinfo) == 0:
result = sysinfo.cpuCount.int
else:
result = sysconf(SC_NPROCESSORS_ONLN)
if result < 0: result = 0
when defined(haiku):
type
SystemInfo {.importc: "system_info", header: "<OS.h>".} = object
cpuCount {.importc: "cpu_count".}: uint32
proc getSystemInfo(info: ptr SystemInfo): int32 {.importc: "get_system_info",
header: "<OS.h>".}
proc countProcessors*(): int {.rtl, extern: "ncpi$1".} =
## Returns the number of the processors/cores the machine has.
## Returns 0 if it cannot be detected.
when defined(windows):
var
si: SystemInfo
getSystemInfo(addr si)
result = int(si.dwNumberOfProcessors)
elif defined(macosx) or defined(bsd):
var
mib: array[0..3, cint]
numCPU: int
mib[0] = CTL_HW
mib[1] = HW_AVAILCPU
var len = sizeof(numCPU).csize_t
discard sysctl(addr(mib), 2, addr(numCPU), len, nil, 0)
if numCPU < 1:
mib[1] = HW_NCPU
discard sysctl(addr(mib), 2, addr(numCPU), len, nil, 0)
result = numCPU
elif defined(hpux):
result = mpctl(MPC_GETNUMSPUS, nil, nil)
elif defined(irix):
var SC_NPROC_ONLN {.importc: "_SC_NPROC_ONLN", header: "<unistd.h>".}: cint
result = sysconf(SC_NPROC_ONLN)
elif defined(genode):
result = runtimeEnv.affinitySpaceTotal().int
elif defined(haiku):
var sysinfo: SystemInfo
if getSystemInfo(addr sysinfo) == 0:
result = sysinfo.cpuCount.int
else:
result = sysconf(SC_NPROCESSORS_ONLN)
if result <= 0: result = 0
countProcessorsImpl()

View File

@@ -127,6 +127,7 @@ type
BSD
FreeBSD
NetBSD
OpenBSD
DragonFlyBSD
@@ -168,7 +169,7 @@ proc detectOsImpl(d: Distribution): bool =
else:
when defined(bsd):
case d
of Distribution.FreeBSD, Distribution.OpenBSD:
of Distribution.FreeBSD, Distribution.NetBSD, Distribution.OpenBSD:
result = $d in uname()
else:
result = false
@@ -251,7 +252,7 @@ proc foreignDepInstallCmd*(foreignPackageName: string): (string, bool) =
result = ("nix-env -i " & p, false)
elif detectOs(Solaris) or detectOs(FreeBSD):
result = ("pkg install " & p, true)
elif detectOs(OpenBSD):
elif detectOs(NetBSD) or detectOs(OpenBSD):
result = ("pkg_add " & p, true)
elif detectOs(PCLinuxOS):
result = ("rpm -ivh " & p, true)

View File

@@ -12,7 +12,7 @@
## The types, vars and procs are bindings for the C standard library
## [<fenv.h>](https://en.cppreference.com/w/c/numeric/fenv) header.
when defined(posix) and not defined(genode):
when defined(posix) and not defined(genode) and not defined(macosx):
{.passl: "-lm".}
var

View File

@@ -190,7 +190,7 @@ proc hashData*(data: pointer, size: int): Hash =
var h: Hash = 0
when defined(js):
var p: cstring
{.emit: """`p` = `Data`""".}
{.emit: """`p` = `Data`;""".}
else:
var p = cast[cstring](data)
var i = 0
@@ -379,6 +379,158 @@ proc hashVmImplChar(x: openArray[char], sPos, ePos: int): Hash =
proc hashVmImplByte(x: openArray[byte], sPos, ePos: int): Hash =
raiseAssert "implementation override in compiler/vmops.nim"
const k0 = 0xc3a5c85c97cb3127u64 # Primes on (2^63, 2^64) for various uses
const k1 = 0xb492b66fbe98f273u64
const k2 = 0x9ae16a3b2f90404fu64
proc load4e(s: openArray[byte], o=0): uint32 {.inline.} =
uint32(s[o + 3]) shl 24 or uint32(s[o + 2]) shl 16 or
uint32(s[o + 1]) shl 8 or uint32(s[o + 0])
proc load8e(s: openArray[byte], o=0): uint64 {.inline.} =
uint64(s[o + 7]) shl 56 or uint64(s[o + 6]) shl 48 or
uint64(s[o + 5]) shl 40 or uint64(s[o + 4]) shl 32 or
uint64(s[o + 3]) shl 24 or uint64(s[o + 2]) shl 16 or
uint64(s[o + 1]) shl 8 or uint64(s[o + 0])
proc load4(s: openArray[byte], o=0): uint32 {.inline.} =
when nimvm: result = load4e(s, o)
else:
when declared copyMem: copyMem result.addr, s[o].addr, result.sizeof
else: result = load4e(s, o)
proc load8(s: openArray[byte], o=0): uint64 {.inline.} =
when nimvm: result = load8e(s, o)
else:
when declared copyMem: copyMem result.addr, s[o].addr, result.sizeof
else: result = load8e(s, o)
proc lenU(s: openArray[byte]): uint64 {.inline.} = s.len.uint64
proc shiftMix(v: uint64): uint64 {.inline.} = v xor (v shr 47)
proc rotR(v: uint64; bits: cint): uint64 {.inline.} =
(v shr bits) or (v shl (64 - bits))
proc len16(u: uint64; v: uint64; mul: uint64): uint64 {.inline.} =
var a = (u xor v)*mul
a = a xor (a shr 47)
var b = (v xor a)*mul
b = b xor (b shr 47)
b*mul
proc len0_16(s: openArray[byte]): uint64 {.inline.} =
if s.len >= 8:
let mul = k2 + 2*s.lenU
let a = load8(s) + k2
let b = load8(s, s.len - 8)
let c = rotR(b, 37)*mul + a
let d = (rotR(a, 25) + b)*mul
len16 c, d, mul
elif s.len >= 4:
let mul = k2 + 2*s.lenU
let a = load4(s).uint64
len16 s.lenU + (a shl 3), load4(s, s.len - 4), mul
elif s.len > 0:
let a = uint32(s[0])
let b = uint32(s[s.len shr 1])
let c = uint32(s[s.len - 1])
let y = a + (b shl 8)
let z = s.lenU + (c shl 2)
shiftMix(y*k2 xor z*k0)*k2
else: k2 # s.len == 0
proc len17_32(s: openArray[byte]): uint64 {.inline.} =
let mul = k2 + 2*s.lenU
let a = load8(s)*k1
let b = load8(s, 8)
let c = load8(s, s.len - 8)*mul
let d = load8(s, s.len - 16)*k2
len16 rotR(a + b, 43) + rotR(c, 30) + d, a + rotR(b + k2, 18) + c, mul
proc len33_64(s: openArray[byte]): uint64 {.inline.} =
let mul = k2 + 2*s.lenU
let a = load8(s)*k2
let b = load8(s, 8)
let c = load8(s, s.len - 8)*mul
let d = load8(s, s.len - 16)*k2
let y = rotR(a + b, 43) + rotR(c, 30) + d
let z = len16(y, a + rotR(b + k2, 18) + c, mul)
let e = load8(s, 16)*mul
let f = load8(s, 24)
let g = (y + load8(s, s.len - 32))*mul
let h = (z + load8(s, s.len - 24))*mul
len16 rotR(e + f, 43) + rotR(g, 30) + h, e + rotR(f + a, 18) + g, mul
type Pair = tuple[first, second: uint64]
proc weakLen32withSeeds2(w, x, y, z, a, b: uint64): Pair {.inline.} =
var a = a + w
var b = rotR(b + a + z, 21)
let c = a
a += x
a += y
b += rotR(a, 44)
result[0] = a + z
result[1] = b + c
proc weakLen32withSeeds(s: openArray[byte]; o: int; a,b: uint64): Pair {.inline.} =
weakLen32withSeeds2 load8(s, o ), load8(s, o + 8),
load8(s, o + 16), load8(s, o + 24), a, b
proc hashFarm(s: openArray[byte]): uint64 {.inline.} =
if s.len <= 16: return len0_16(s)
if s.len <= 32: return len17_32(s)
if s.len <= 64: return len33_64(s)
const seed = 81u64 # not const to use input `h`
var
o = 0 # s[] ptr arith -> variable origin variable `o`
x = seed
y = seed*k1 + 113
z = shiftMix(y*k2 + 113)*k2
v, w: Pair
x = x*k2 + load8(s)
let eos = ((s.len - 1) div 64)*64
let last64 = eos + ((s.len - 1) and 63) - 63
while true:
x = rotR(x + y + v[0] + load8(s, o+8), 37)*k1
y = rotR(y + v[1] + load8(s, o+48), 42)*k1
x = x xor w[1]
y += v[0] + load8(s, o+40)
z = rotR(z + w[0], 33)*k1
v = weakLen32withSeeds(s, o+0 , v[1]*k1, x + w[0])
w = weakLen32withSeeds(s, o+32, z + w[1], y + load8(s, o+16))
swap z, x
inc o, 64
if o == eos: break
let mul = k1 + ((z and 0xff) shl 1)
o = last64
w[0] += (s.lenU - 1) and 63
v[0] += w[0]
w[0] += v[0]
x = rotR(x + y + v[0] + load8(s, o+8), 37)*mul
y = rotR(y + v[1] + load8(s, o+48), 42)*mul
x = x xor w[1]*9
y += v[0]*9 + load8(s, o+40)
z = rotR(z + w[0], 33)*mul
v = weakLen32withSeeds(s, o+0 , v[1]*mul, x + w[0])
w = weakLen32withSeeds(s, o+32, z + w[1], y + load8(s, o+16))
swap z, x
len16 len16(v[0],w[0],mul) + shiftMix(y)*k0 + z, len16(v[1],w[1],mul) + x, mul
template jsNoInt64: untyped =
when defined js:
when compiles(compileOption("jsbigint64")):
when not compileOption("jsbigint64"): true
else: false
else: false
else: false
const sHash2 = (when defined(nimStringHash2) or jsNoInt64(): true else: false)
template maybeFailJS_Number =
when jsNoInt64() and not defined(nimStringHash2):
{.error: "Must use `-d:nimStringHash2` when using `--jsbigint64:off`".}
proc hash*(x: string): Hash =
## Efficient hashing of strings.
##
@@ -387,11 +539,14 @@ proc hash*(x: string): Hash =
## * `hashIgnoreCase <#hashIgnoreCase,string>`_
runnableExamples:
doAssert hash("abracadabra") != hash("AbracadabrA")
when nimvm:
result = hashVmImpl(x, 0, high(x))
maybeFailJS_Number()
when not sHash2:
result = cast[Hash](hashFarm(toOpenArrayByte(x, 0, x.high)))
else:
result = murmurHash(toOpenArrayByte(x, 0, high(x)))
#when nimvm:
# result = hashVmImpl(x, 0, high(x))
when true:
result = murmurHash(toOpenArrayByte(x, 0, high(x)))
proc hash*(x: cstring): Hash =
## Efficient hashing of null-terminated strings.
@@ -400,14 +555,22 @@ proc hash*(x: cstring): Hash =
doAssert hash(cstring"AbracadabrA") == hash("AbracadabrA")
doAssert hash(cstring"abracadabra") != hash(cstring"AbracadabrA")
when nimvm:
hashVmImpl(x, 0, high(x))
else:
when not defined(js):
murmurHash(toOpenArrayByte(x, 0, x.high))
else:
maybeFailJS_Number()
when not sHash2:
when defined js:
let xx = $x
murmurHash(toOpenArrayByte(xx, 0, high(xx)))
result = cast[Hash](hashFarm(toOpenArrayByte(xx, 0, xx.high)))
else:
result = cast[Hash](hashFarm(toOpenArrayByte(x, 0, x.high)))
else:
#when nimvm:
# result = hashVmImpl(x, 0, high(x))
when true:
when not defined(js):
result = murmurHash(toOpenArrayByte(x, 0, x.high))
else:
let xx = $x
result = murmurHash(toOpenArrayByte(xx, 0, high(xx)))
proc hash*(sBuf: string, sPos, ePos: int): Hash =
## Efficient hashing of a string buffer, from starting
@@ -418,7 +581,11 @@ proc hash*(sBuf: string, sPos, ePos: int): Hash =
var a = "abracadabra"
doAssert hash(a, 0, 3) == hash(a, 7, 10)
murmurHash(toOpenArrayByte(sBuf, sPos, ePos))
maybeFailJS_Number()
when not sHash2:
result = cast[Hash](hashFarm(toOpenArrayByte(sBuf, sPos, ePos)))
else:
murmurHash(toOpenArrayByte(sBuf, sPos, ePos))
proc hashIgnoreStyle*(x: string): Hash =
## Efficient hashing of strings; style is ignored.
@@ -553,12 +720,18 @@ proc hash*[A](x: openArray[A]): Hash =
## Efficient hashing of arrays and sequences.
## There must be a `hash` proc defined for the element type `A`.
when A is byte:
result = murmurHash(x)
elif A is char:
when nimvm:
result = hashVmImplChar(x, 0, x.high)
when not sHash2:
result = cast[Hash](hashFarm(x))
else:
result = murmurHash(toOpenArrayByte(x, 0, x.high))
result = murmurHash(x)
elif A is char:
when not sHash2:
result = cast[Hash](hashFarm(toOpenArrayByte(x, 0, x.high)))
else:
#when nimvm:
# result = hashVmImplChar(x, 0, x.high)
when true:
result = murmurHash(toOpenArrayByte(x, 0, x.high))
else:
result = 0
for a in x:
@@ -574,17 +747,24 @@ proc hash*[A](aBuf: openArray[A], sPos, ePos: int): Hash =
runnableExamples:
let a = [1, 2, 5, 1, 2, 6]
doAssert hash(a, 0, 1) == hash(a, 3, 4)
when A is byte:
when nimvm:
result = hashVmImplByte(aBuf, sPos, ePos)
maybeFailJS_Number()
when not sHash2:
result = cast[Hash](hashFarm(toOpenArray(aBuf, sPos, ePos)))
else:
result = murmurHash(toOpenArray(aBuf, sPos, ePos))
#when nimvm:
# result = hashVmImplByte(aBuf, sPos, ePos)
when true:
result = murmurHash(toOpenArray(aBuf, sPos, ePos))
elif A is char:
when nimvm:
result = hashVmImplChar(aBuf, sPos, ePos)
maybeFailJS_Number()
when not sHash2:
result = cast[Hash](hashFarm(toOpenArrayByte(aBuf, sPos, ePos)))
else:
result = murmurHash(toOpenArrayByte(aBuf, sPos, ePos))
#when nimvm:
# result = hashVmImplChar(aBuf, sPos, ePos)
when true:
result = murmurHash(toOpenArrayByte(aBuf, sPos, ePos))
else:
for i in sPos .. ePos:
result = result !& hash(aBuf[i])

View File

@@ -164,7 +164,8 @@ func `[]`*(headers: HttpHeaders, key: string): HttpHeaderValues =
## To access multiple values of a key, use the overloaded `[]` below or
## to get all of them access the `table` field directly.
{.cast(noSideEffect).}:
return headers.table[headers.toCaseInsensitive(key)].HttpHeaderValues
let tmp = headers.table[headers.toCaseInsensitive(key)]
return HttpHeaderValues(tmp)
converter toString*(values: HttpHeaderValues): string =
return seq[string](values)[0]

File diff suppressed because it is too large Load Diff

View File

@@ -1007,7 +1007,7 @@ when defined(js):
{.emit: """for (var property in `x`) {
if (`x`.hasOwnProperty(property)) {
""".}
var nimProperty: cstring
var nimValue: JsObject
{.emit: "`nimProperty` = property; `nimValue` = `x`[property];".}

View File

@@ -104,9 +104,9 @@ proc fillBaseLexer(L: var BaseLexer, pos: int): int =
result = 0
proc handleCR*(L: var BaseLexer, pos: int): int =
## Call this if you scanned over '\c' in the buffer; it returns the
## Call this if you scanned over `'\c'` in the buffer; it returns the
## position to continue the scanning from. `pos` must be the position
## of the '\c'.
## of the `'\c'`.
assert(L.buf[pos] == '\c')
inc(L.lineNumber)
result = fillBaseLexer(L, pos)
@@ -115,9 +115,9 @@ proc handleCR*(L: var BaseLexer, pos: int): int =
L.lineStart = result
proc handleLF*(L: var BaseLexer, pos: int): int =
## Call this if you scanned over '\L' in the buffer; it returns the
## Call this if you scanned over `'\L'` in the buffer; it returns the
## position to continue the scanning from. `pos` must be the position
## of the '\L'.
## of the `'\L'`.
assert(L.buf[pos] == '\L')
inc(L.lineNumber)
result = fillBaseLexer(L, pos) #L.lastNL := result-1; // BUGFIX: was: result;

View File

@@ -58,6 +58,7 @@ import std/private/since
# of the standard library!
import std/[bitops, fenv]
import system/countbits_impl
when defined(nimPreviewSlimSystem):
import std/assertions
@@ -155,7 +156,7 @@ func fac*(n: int): int =
{.push checks: off, line_dir: off, stack_trace: off.}
when defined(posix) and not defined(genode):
when defined(posix) and not defined(genode) and not defined(macosx):
{.passl: "-lm".}
const
@@ -224,7 +225,7 @@ when defined(js):
return (num & ~(1 << bitPos)) | (bitVal << bitPos);
}
`b`[1] = updateBit(`b`[1], 31, `sgn`);
`result` = `a`[0]
`result` = `a`[0];
""".}
proc signbit*(x: SomeFloat): bool {.inline, since: (1, 5, 1).} =
@@ -1229,40 +1230,42 @@ func gcd*[T](x, y: T): T =
swap x, y
abs x
func gcd*(x, y: SomeInteger): SomeInteger =
## Computes the greatest common (positive) divisor of `x` and `y`,
## using the binary GCD (aka Stein's) algorithm.
##
## **See also:**
## * `gcd func <#gcd,T,T>`_ for a float version
## * `lcm func <#lcm,T,T>`_
runnableExamples:
doAssert gcd(12, 8) == 4
doAssert gcd(17, 63) == 1
when x is SomeSignedInt:
var x = abs(x)
else:
var x = x
when y is SomeSignedInt:
var y = abs(y)
else:
var y = y
if x == 0:
return y
if y == 0:
return x
let shift = countTrailingZeroBits(x or y)
y = y shr countTrailingZeroBits(y)
while x != 0:
x = x shr countTrailingZeroBits(x)
if y > x:
swap y, x
x -= y
y shl shift
when useBuiltins:
## this func uses bitwise comparisons from C compilers, which are not always available.
func gcd*(x, y: SomeInteger): SomeInteger =
## Computes the greatest common (positive) divisor of `x` and `y`,
## using the binary GCD (aka Stein's) algorithm.
##
## **See also:**
## * `gcd func <#gcd,T,T>`_ for a float version
## * `lcm func <#lcm,T,T>`_
runnableExamples:
doAssert gcd(12, 8) == 4
doAssert gcd(17, 63) == 1
when x is SomeSignedInt:
var x = abs(x)
else:
var x = x
when y is SomeSignedInt:
var y = abs(y)
else:
var y = y
if x == 0:
return y
if y == 0:
return x
let shift = countTrailingZeroBits(x or y)
y = y shr countTrailingZeroBits(y)
while x != 0:
x = x shr countTrailingZeroBits(x)
if y > x:
swap y, x
x -= y
y shl shift
func gcd*[T](x: openArray[T]): T {.since: (1, 1).} =
## Computes the greatest common (positive) divisor of the elements of `x`.
##

View File

@@ -35,12 +35,12 @@ proc newEIO(msg: string): ref IOError =
new(result)
result.msg = msg
proc setFileSize(fh: FileHandle, newFileSize = -1): OSErrorCode =
## Set the size of open file pointed to by `fh` to `newFileSize` if != -1.
## Space is only allocated if that is cheaper than writing to the file. This
## routine returns the last OSErrorCode found rather than raising to support
## old rollback/clean-up code style. [ Should maybe move to std/osfiles. ]
if newFileSize == -1:
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. ]
if newFileSize < 0 or newFileSize == oldSize:
return
when defined(windows):
var sizeHigh = int32(newFileSize shr 32)
@@ -51,14 +51,18 @@ proc setFileSize(fh: FileHandle, newFileSize = -1): OSErrorCode =
setEndOfFile(fh) == 0:
result = lastErr
else:
var e: cint # posix_fallocate truncates up when needed.
when declared(posix_fallocate):
while (e = posix_fallocate(fh, 0, newFileSize); e == EINTR):
discard
if e in [EINVAL, EOPNOTSUPP] and ftruncate(fh, newFileSize) == -1:
result = osLastError() # fallback arguable; Most portable, but allows SEGV
elif e != 0:
result = osLastError()
if newFileSize > oldSize: # grow the file
var e: cint # posix_fallocate truncates up when needed.
when declared(posix_fallocate):
while (e = posix_fallocate(fh, 0, newFileSize); e == EINTR):
discard
if e in [EINVAL, EOPNOTSUPP] and ftruncate(fh, newFileSize) == -1:
result = osLastError() # fallback arguable; Most portable BUT allows SEGV
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
@@ -255,41 +259,31 @@ proc open*(filename: string, mode: FileMode = fmRead,
flags = flags or O_CREAT or O_TRUNC
var permissionsMode = S_IRUSR or S_IWUSR
result.handle = open(filename, flags, permissionsMode)
if result.handle != -1:
if (let e = setFileSize(result.handle.FileHandle, newFileSize);
e != 0.OSErrorCode): fail(e, "error setting file size")
else:
result.handle = open(filename, flags)
if result.handle == -1:
# XXX: errno is supposed to be set here
# Is there an exception that wraps it?
fail(osLastError(), "error opening file")
if (let e = setFileSize(result.handle.FileHandle, newFileSize);
e != 0.OSErrorCode): fail(e, "error setting file size")
if mappedSize != -1:
result.size = mappedSize
else:
var stat: Stat
if mappedSize != -1: #XXX Logic here differs from `when windows` branch ..
result.size = mappedSize #.. which always fstats&Uses min(mappedSize, st).
else: # if newFileSize!=-1: result.size=newFileSize # if trust setFileSize
var stat: Stat #^^.. BUT some FSes (eg. Linux HugeTLBfs) round to 2MiB.
if fstat(result.handle, stat) != -1:
# XXX: Hmm, this could be unsafe
# Why is mmap taking int anyway?
result.size = int(stat.st_size)
result.size = stat.st_size.int # int may be 32-bit-unsafe for 2..<4 GiB
else:
fail(osLastError(), "error getting file size")
result.flags = if mapFlags == cint(-1): MAP_SHARED else: mapFlags
#Ensure exactly one of MAP_PRIVATE cr MAP_SHARED is set
# Ensure exactly one of MAP_PRIVATE cr MAP_SHARED is set
if int(result.flags and MAP_PRIVATE) == 0:
result.flags = result.flags or MAP_SHARED
result.mem = mmap(
nil,
result.size,
if readonly: PROT_READ else: PROT_READ or PROT_WRITE,
result.flags,
result.handle,
offset)
let pr = if readonly: PROT_READ else: PROT_READ or PROT_WRITE
result.mem = mmap(nil, result.size, pr, result.flags, result.handle, offset)
if result.mem == cast[pointer](MAP_FAILED):
fail(osLastError(), "file mapping failed")
@@ -353,7 +347,7 @@ proc resize*(f: var MemFile, newFileSize: int) {.raises: [IOError, OSError].} =
raise newException(IOError,
"Cannot resize MemFile opened with allowRemap=false")
if newFileSize != f.size:
if (let e = setFileSize(f.handle.FileHandle, newFileSize);
if (let e = setFileSize(f.handle.FileHandle, newFileSize, f.size);
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.

View File

@@ -432,6 +432,10 @@ const mimes* = {
"msty": "application/vnd.muvee.style",
"taglet": "application/vnd.mynfc",
"nlu": "application/vnd.neurolanguage.nlu",
"nim": "text/nim",
"nimble": "text/nimble",
"nimf": "text/nim",
"nims": "text/nim",
"ntf": "application/vnd.nitf",
"nitf": "application/vnd.nitf",
"nnd": "application/vnd.noblenet-directory",

View File

@@ -2040,8 +2040,10 @@ proc dial*(address: string, port: Port,
if success:
result = newSocket(lastFd, domain, sockType, protocol, buffered)
elif lastError != 0.OSErrorCode:
lastFd.close()
raiseOSError(lastError)
else:
lastFd.close()
raise newException(IOError, "Couldn't resolve address: " & address)
proc connect*(socket: Socket, address: string,

View File

@@ -117,9 +117,10 @@ proc option*[T](val: sink T): Option[T] {.inline.} =
assert option[Foo](nil).isNone
assert option(42).isSome
result.val = val
when T isnot SomePointer:
result.has = true
when T is SomePointer:
result = Option[T](val: val)
else:
result = Option[T](has: true, val: val)
proc some*[T](val: sink T): Option[T] {.inline.} =
## Returns an `Option` that has the value `val`.
@@ -136,10 +137,9 @@ proc some*[T](val: sink T): Option[T] {.inline.} =
when T is SomePointer:
assert not val.isNil
result.val = val
result = Option[T](val: val)
else:
result.has = true
result.val = val
result = Option[T](has: true, val: val)
proc none*(T: typedesc): Option[T] {.inline.} =
## Returns an `Option` for this type that has no value.

View File

@@ -692,7 +692,10 @@ proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noWeirdT
proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noWeirdTarget.} =
## Sleeps `milsecs` milliseconds.
## A negative `milsecs` causes sleep to return immediately.
when defined(windows):
if milsecs < 0:
return # fixes #23732
winlean.sleep(int32(milsecs))
else:
var a, b: Timespec
@@ -754,14 +757,14 @@ template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
## 'rawInfo' is either a 'BY_HANDLE_FILE_INFORMATION' structure on Windows,
## or a 'Stat' structure on posix
when defined(windows):
template merge(a, b): untyped =
int64(
template merge[T](a, b): untyped =
cast[T](
(uint64(cast[uint32](a))) or
(uint64(cast[uint32](b)) shl 32)
)
formalInfo.id.device = rawInfo.dwVolumeSerialNumber
formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh)
formalInfo.size = merge(rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh)
formalInfo.id.file = merge[FileId](rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh)
formalInfo.size = merge[BiggestInt](rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh)
formalInfo.linkCount = rawInfo.nNumberOfLinks
formalInfo.lastAccessTime = fromWinTime(rdFileTime(rawInfo.ftLastAccessTime))
formalInfo.lastWriteTime = fromWinTime(rdFileTime(rawInfo.ftLastWriteTime))

View File

@@ -212,7 +212,7 @@ proc processID*(p: Process): int {.rtl, extern: "nosp$1".} =
return p.id
proc waitForExit*(p: Process, timeout: int = -1): int {.rtl,
extern: "nosp$1", raises: [OSError, ValueError], tags: [].}
extern: "nosp$1", raises: [OSError, ValueError], tags: [TimeEffect].}
## Waits for the process to finish and returns `p`'s error code.
##
## .. warning:: Be careful when using `waitForExit` for processes created without
@@ -457,7 +457,7 @@ proc execProcesses*(cmds: openArray[string],
if afterRunEvent != nil: afterRunEvent(i, p)
close(p)
iterator lines*(p: Process, keepNewLines = false): string {.since: (1, 3), raises: [OSError, IOError, ValueError], tags: [ReadIOEffect].} =
iterator lines*(p: Process, keepNewLines = false): string {.since: (1, 3), raises: [OSError, IOError, ValueError], tags: [ReadIOEffect, TimeEffect].} =
## Convenience iterator for working with `startProcess` to read data from a
## background process.
##
@@ -487,7 +487,7 @@ iterator lines*(p: Process, keepNewLines = false): string {.since: (1, 3), raise
discard waitForExit(p)
proc readLines*(p: Process): (seq[string], int) {.since: (1, 3),
raises: [OSError, IOError, ValueError], tags: [ReadIOEffect].} =
raises: [OSError, IOError, ValueError], tags: [ReadIOEffect, TimeEffect].} =
## Convenience function for working with `startProcess` to read data from a
## background process.
##
@@ -1353,114 +1353,63 @@ elif not defined(useNimRtl):
result = exitStatusLikeShell(p.exitStatus)
else:
import std/times
const
hasThreadSupport = compileOption("threads") and not defined(nimscript)
import std/times except getTime
import std/monotimes
proc waitForExit(p: Process, timeout: int = -1): int =
template adjustTimeout(t, s, e: Timespec) =
var diff: int
var b: Timespec
b.tv_sec = e.tv_sec
b.tv_nsec = e.tv_nsec
e.tv_sec = e.tv_sec - s.tv_sec
if e.tv_nsec >= s.tv_nsec:
e.tv_nsec -= s.tv_nsec
else:
if e.tv_sec == posix.Time(0):
raise newException(ValueError, "System time was modified")
else:
diff = s.tv_nsec - e.tv_nsec
e.tv_nsec = 1_000_000_000 - diff
t.tv_sec = t.tv_sec - e.tv_sec
if t.tv_nsec >= e.tv_nsec:
t.tv_nsec -= e.tv_nsec
else:
t.tv_sec = t.tv_sec - posix.Time(1)
diff = e.tv_nsec - t.tv_nsec
t.tv_nsec = 1_000_000_000 - diff
s.tv_sec = b.tv_sec
s.tv_nsec = b.tv_nsec
if p.exitFlag:
return exitStatusLikeShell(p.exitStatus)
if timeout == -1:
var status: cint = 1
if timeout < 0:
# Backwards compatibility with previous verison to
# handle cases where timeout == -1, but extend
# to handle cases where timeout < 0
var status: cint
if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status
else:
var nmask, omask: Sigset
var sinfo: SigInfo
var stspec, enspec, tmspec: Timespec
discard sigemptyset(nmask)
discard sigemptyset(omask)
discard sigaddset(nmask, SIGCHLD)
when hasThreadSupport:
if pthread_sigmask(SIG_BLOCK, nmask, omask) == -1:
raiseOSError(osLastError())
else:
if sigprocmask(SIG_BLOCK, nmask, omask) == -1:
raiseOSError(osLastError())
if timeout >= 1000:
tmspec.tv_sec = posix.Time(timeout div 1_000)
tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
else:
tmspec.tv_sec = posix.Time(0)
tmspec.tv_nsec = (timeout * 1_000_000)
try:
if clock_gettime(CLOCK_REALTIME, stspec) == -1:
raiseOSError(osLastError())
while true:
let res = sigtimedwait(nmask, sinfo, tmspec)
if res == SIGCHLD:
if sinfo.si_pid == p.id:
var status: cint = 1
if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status
break
else:
# we have SIGCHLD, but not for process we are waiting,
# so we need to adjust timeout value and continue
if clock_gettime(CLOCK_REALTIME, enspec) == -1:
raiseOSError(osLastError())
adjustTimeout(tmspec, stspec, enspec)
elif res < 0:
let err = osLastError()
if err.cint == EINTR:
# we have received another signal, so we need to
# adjust timeout and continue
if clock_gettime(CLOCK_REALTIME, enspec) == -1:
raiseOSError(osLastError())
adjustTimeout(tmspec, stspec, enspec)
elif err.cint == EAGAIN:
# timeout expired, so we trying to kill process
if posix.kill(p.id, SIGKILL) == -1:
raiseOSError(osLastError())
var status: cint = 1
if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status
break
else:
raiseOSError(err)
finally:
when hasThreadSupport:
if pthread_sigmask(SIG_UNBLOCK, nmask, omask) == -1:
raiseOSError(osLastError())
# Max 50ms delay
const maxWait = initDuration(milliseconds = 50)
let wait = initDuration(milliseconds = timeout)
let deadline = getMonoTime() + wait
# starting 50μs delay
var delay = initDuration(microseconds = 50)
while true:
var status: cint
let pid = waitpid(p.id, status, WNOHANG)
if p.id == pid :
p.exitFlag = true
p.exitStatus = status
break
elif pid.int == -1:
raiseOsError(osLastError())
else:
if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1:
raiseOSError(osLastError())
# Continue waiting if needed
if getMonoTime() >= deadline:
# Previous version of `waitForExit`
# foricibly killed the process.
# We keep this so we don't break programs
# that depend on this behavior
if posix.kill(p.id, SIGKILL) < 0:
raiseOSError(osLastError())
else:
const max = 1_000_000_000
let
newWait = getMonoTime() + delay
ticks = newWait.ticks()
ns = ticks mod max
secs = ticks div max
var
waitSpec: TimeSpec
unused: Timespec
waitSpec.tv_sec = posix.Time(secs)
waitSpec.tv_nsec = clong ns
discard posix.clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, waitSpec, unused)
let remaining = deadline - getMonoTime()
delay = min([delay * 2, remaining, maxWait])
result = exitStatusLikeShell(p.exitStatus)

View File

@@ -176,6 +176,7 @@
include "system/inclrtl"
import std/strutils
import std/os
type
@@ -249,9 +250,20 @@ proc initOptParser*(cmdline: seq[string], shortNoVal: set[char] = {},
result.cmds[i] = cmdline[i]
else:
when declared(paramCount):
result.cmds = newSeq[string](paramCount())
for i in countup(1, paramCount()):
result.cmds[i-1] = paramStr(i)
when defined(nimscript):
var ctr = 0
var firstNimsFound = false
for i in countup(0, paramCount()):
if firstNimsFound:
result.cmds[ctr] = paramStr(i)
inc ctr, 1
if paramStr(i).endsWith(".nims") and not firstNimsFound:
firstNimsFound = true
result.cmds = newSeq[string](paramCount()-i)
else:
result.cmds = newSeq[string](paramCount())
for i in countup(1, paramCount()):
result.cmds[i-1] = paramStr(i)
else:
# we cannot provide this for NimRtl creation on Posix, because we can't
# access the command line arguments then!

View File

@@ -460,6 +460,8 @@ proc parseBiggestInt*(s: openArray[char], number: var BiggestInt): int {.
var res: BiggestInt
doAssert parseBiggestInt("9223372036854775807", res) == 19
doAssert res == 9223372036854775807
doAssert parseBiggestInt("-2024_05_09", res) == 11
doAssert res == -20240509
var res = BiggestInt(0)
# use 'res' for exception safety (don't write to 'number' in case of an
# overflow exception):
@@ -474,10 +476,8 @@ proc parseInt*(s: openArray[char], number: var int): int {.
## `ValueError` is raised if the parsed integer is out of the valid range.
runnableExamples:
var res: int
doAssert parseInt("2019", res, 0) == 4
doAssert res == 2019
doAssert parseInt("2019", res, 2) == 2
doAssert res == 19
doAssert parseInt("-2024_05_02", res) == 11
doAssert res == -20240502
var res = BiggestInt(0)
result = parseBiggestInt(s, res)
when sizeof(int) <= 4:
@@ -992,6 +992,10 @@ proc parseBiggestInt*(s: string, number: var BiggestInt, start = 0): int {.noSid
var res: BiggestInt
doAssert parseBiggestInt("9223372036854775807", res, 0) == 19
doAssert res == 9223372036854775807
doAssert parseBiggestInt("-2024_05_09", res) == 11
doAssert res == -20240509
doAssert parseBiggestInt("-2024_05_02", res, 7) == 4
doAssert res == 502
parseBiggestInt(s.toOpenArray(start, s.high), number)
proc parseInt*(s: string, number: var int, start = 0): int {.noSideEffect, raises: [ValueError].} =
@@ -1000,10 +1004,10 @@ proc parseInt*(s: string, number: var int, start = 0): int {.noSideEffect, raise
## `ValueError` is raised if the parsed integer is out of the valid range.
runnableExamples:
var res: int
doAssert parseInt("2019", res, 0) == 4
doAssert res == 2019
doAssert parseInt("2019", res, 2) == 2
doAssert res == 19
doAssert parseInt("-2024_05_02", res) == 11
doAssert res == -20240502
doAssert parseInt("-2024_05_02", res, 7) == 4
doAssert res == 502
parseInt(s.toOpenArray(start, s.high), number)

View File

@@ -27,20 +27,17 @@ func expandTabs*(s: string, tabSize: int = 8): string =
doAssert expandTabs("a\tb\n\txy\t", 3) == "a b\n xy "
result = newStringOfCap(s.len + s.len shr 2)
var pos = 0
template addSpaces(n) =
for j in 0 ..< n:
for _ in 1..n:
result.add(' ')
pos += 1
pos += n
for i in 0 ..< len(s):
let c = s[i]
var pos = 0
let denominator = if tabSize > 0: tabSize else: 1
for c in s:
if c == '\t':
let
denominator = if tabSize > 0: tabSize else: 1
numSpaces = tabSize - pos mod denominator
let numSpaces = tabSize - pos mod denominator
addSpaces(numSpaces)
else:
result.add(c)

View File

@@ -334,9 +334,9 @@ func normalize*(s: string): string {.rtl, extern: "nsuNormalize".} =
func cmpIgnoreCase*(a, b: string): int {.rtl, extern: "nsuCmpIgnoreCase".} =
## Compares two strings in a case insensitive manner. Returns:
##
## | 0 if a == b
## | < 0 if a < b
## | > 0 if a > b
## | `0` if a == b
## | `< 0` if a < b
## | `> 0` if a > b
runnableExamples:
doAssert cmpIgnoreCase("FooBar", "foobar") == 0
doAssert cmpIgnoreCase("bar", "Foo") < 0
@@ -354,9 +354,9 @@ func cmpIgnoreStyle*(a, b: string): int {.rtl, extern: "nsuCmpIgnoreStyle".} =
##
## Returns:
##
## | 0 if a == b
## | < 0 if a < b
## | > 0 if a > b
## | `0` if a == b
## | `< 0` if a < b
## | `> 0` if a > b
runnableExamples:
doAssert cmpIgnoreStyle("foo_bar", "FooBar") == 0
doAssert cmpIgnoreStyle("foo_bar_5", "FooBar4") > 0
@@ -565,7 +565,7 @@ iterator rsplit*(s: string, sep: char,
maxsplit: int = -1): string =
## Splits the string `s` into substrings from the right using a
## string separator. Works exactly the same as `split iterator
## <#split.i,string,char,int>`_ except in reverse order.
## <#split.i,string,char,int>`_ except in **reverse** order.
##
## ```nim
## for piece in "foo:bar".rsplit(':'):
@@ -592,7 +592,7 @@ iterator rsplit*(s: string, seps: set[char] = Whitespace,
maxsplit: int = -1): string =
## Splits the string `s` into substrings from the right using a
## string separator. Works exactly the same as `split iterator
## <#split.i,string,char,int>`_ except in reverse order.
## <#split.i,string,char,int>`_ except in **reverse** order.
##
## ```nim
## for piece in "foo bar".rsplit(WhiteSpace):
@@ -622,7 +622,7 @@ iterator rsplit*(s: string, sep: string, maxsplit: int = -1,
keepSeparators: bool = false): string =
## Splits the string `s` into substrings from the right using a
## string separator. Works exactly the same as `split iterator
## <#split.i,string,string,int>`_ except in reverse order.
## <#split.i,string,string,int>`_ except in **reverse** order.
##
## ```nim
## for piece in "foothebar".rsplit("the"):
@@ -805,7 +805,7 @@ func split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.rtl,
func rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string] {.rtl,
extern: "nsuRSplitChar".} =
## The same as the `rsplit iterator <#rsplit.i,string,char,int>`_, but is a func
## that returns a sequence of substrings.
## that returns a sequence of substrings in original order.
##
## A possible common use case for `rsplit` is path manipulation,
## particularly on systems that don't use a common delimiter.
@@ -835,7 +835,7 @@ func rsplit*(s: string, seps: set[char] = Whitespace,
maxsplit: int = -1): seq[string]
{.rtl, extern: "nsuRSplitCharSet".} =
## The same as the `rsplit iterator <#rsplit.i,string,set[char],int>`_, but is a
## func that returns a sequence of substrings.
## func that returns a sequence of substrings in original order.
##
## A possible common use case for `rsplit` is path manipulation,
## particularly on systems that don't use a common delimiter.
@@ -867,7 +867,7 @@ func rsplit*(s: string, seps: set[char] = Whitespace,
func rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] {.rtl,
extern: "nsuRSplitString".} =
## The same as the `rsplit iterator <#rsplit.i,string,string,int,bool>`_, but is a func
## that returns a sequence of substrings.
## that returns a sequence of substrings in original order.
##
## A possible common use case for `rsplit` is path manipulation,
## particularly on systems that don't use a common delimiter.
@@ -1307,7 +1307,7 @@ func parseEnum*[T: enum](s: string): T =
## type contains multiple fields with the same string value.
##
## Raises `ValueError` for an invalid value in `s`. The comparison is
## done in a style insensitive way.
## done in a style insensitive way (first letter is still case-sensitive).
runnableExamples:
type
MyEnum = enum
@@ -1327,7 +1327,7 @@ func parseEnum*[T: enum](s: string, default: T): T =
## type contains multiple fields with the same string value.
##
## Uses `default` for an invalid value in `s`. The comparison is done in a
## style insensitive way.
## style insensitive way (first letter is still case-sensitive).
runnableExamples:
type
MyEnum = enum

View File

@@ -345,7 +345,7 @@ proc collectImpl(init, body: NimNode): NimNode {.since: (1, 1).} =
let res = genSym(nskVar, "collectResult")
var bracketExpr: NimNode
if init != nil:
expectKind init, {nnkCall, nnkIdent, nnkSym, nnkClosedSymChoice, nnkOpenSymChoice}
expectKind init, {nnkCall, nnkIdent, nnkSym, nnkClosedSymChoice, nnkOpenSymChoice, nnkOpenSym}
bracketExpr = newTree(nnkBracketExpr,
if init.kind in {nnkCall, nnkClosedSymChoice, nnkOpenSymChoice}:
freshIdentNodes(init[0]) else: freshIdentNodes(init))

View File

@@ -131,9 +131,10 @@
=========== ================================================================================= ==============================================
Other strings can be inserted by putting them in `''`. For example
`hh'->'mm` will give `01->56`. The following characters can be
inserted without quoting them: `:` `-` `(` `)` `/` `[` `]`
`,`. A literal `'` can be specified with `''`.
`hh'->'mm` will give `01->56`. In addition to spaces,
the following characters can be inserted without quoting them:
`:` `-` `,` `.` `(` `)` `/` `[` `]`.
A literal `'` can be specified with `''`.
However you don't need to necessarily separate format patterns, as an
unambiguous format string like `yyyyMMddhhmmss` is also valid (although
@@ -1498,11 +1499,11 @@ proc `-=`*(a: var DateTime, b: Duration) =
a = a - b
proc getDateStr*(dt = now()): string {.rtl, extern: "nt$1", tags: [TimeEffect].} =
## Gets the current local date as a string of the format `YYYY-MM-DD`.
## Gets the current local date as a string of the format `YYYY-MM-dd`.
runnableExamples:
echo getDateStr(now() - 1.months)
assertDateTimeInitialized dt
result = newStringOfCap(10) # len("YYYY-MM-DD") == 10
result = newStringOfCap(10) # len("YYYY-MM-dd") == 10
result.addInt dt.year
result.add '-'
result.add intToStr(dt.monthZero, 2)
@@ -1631,7 +1632,7 @@ const
"Sunday"],
)
FormatLiterals = {' ', '-', '/', ':', '(', ')', '[', ']', ','}
FormatLiterals = {' ', '-', '/', ':', '(', ')', '[', ']', ',', '.'}
proc `$`*(f: TimeFormat): string =
## Returns the format string that was used to construct `f`.

View File

@@ -836,9 +836,9 @@ proc toRunes*(s: openArray[char]): seq[Rune] =
proc cmpRunesIgnoreCase*(a, b: openArray[char]): int {.rtl, extern: "nuc$1".} =
## Compares two UTF-8 strings and ignores the case. Returns:
##
## | 0 if a == b
## | < 0 if a < b
## | > 0 if a > b
## | `0` if a == b
## | `< 0` if a < b
## | `> 0` if a > b
var i = 0
var j = 0
var ar, br: Rune
@@ -1375,9 +1375,9 @@ proc toRunes*(s: string): seq[Rune] {.inline.} =
proc cmpRunesIgnoreCase*(a, b: string): int {.inline.} =
## Compares two UTF-8 strings and ignores the case. Returns:
##
## | 0 if a == b
## | < 0 if a < b
## | > 0 if a > b
## | `0` if a == b
## | `< 0` if a < b
## | `> 0` if a > b
cmpRunesIgnoreCase(a.toOa(), b.toOa())
proc reversed*(s: string): string {.inline.} =

View File

@@ -109,11 +109,11 @@ when defined(js):
return n.toString().match(/^-?\d+$/);
}
if (Number.isSafeInteger(`a`))
`result` = `a` === 0 && 1 / `a` < 0 ? "-0.0" : `a`+".0"
`result` = `a` === 0 && 1 / `a` < 0 ? "-0.0" : `a`+".0";
else {
`result` = `a`+""
`result` = `a`+"";
if(nimOnlyDigitsOrMinus(`result`)){
`result` = `a`+".0"
`result` = `a`+".0";
}
}
""".}

View File

@@ -9,7 +9,7 @@ export osseps
import std/envvars
import std/private/osappdirs
import std/pathnorm
import std/[pathnorm, hashes, sugar, strutils]
from std/private/ospaths2 import joinPath, splitPath,
ReadDirEffect, WriteDirEffect,
@@ -25,6 +25,16 @@ export ReadDirEffect, WriteDirEffect
type
Path* = distinct string
func hash*(x: Path): Hash =
let x = x.string.dup(normalizePath)
if FileSystemCaseSensitive:
result = x.hash
else:
result = x.toLowerAscii.hash
template `$`*(x: Path): string =
string(x)
func `==`*(x, y: Path): bool {.inline.} =
## Compares two paths.
##

View File

@@ -37,13 +37,13 @@ when defined(js):
let a = array[2, float64].default
assert jsConstructorName(a) == "Float64Array"
assert jsConstructorName(a.toJs) == "Float64Array"
asm """`result` = `a`.constructor.name"""
{.emit: """`result` = `a`.constructor.name;""".}
proc hasJsBigInt*(): bool =
asm """`result` = typeof BigInt != 'undefined'"""
{.emit: """`result` = typeof BigInt != 'undefined';""".}
proc hasBigUint64Array*(): bool =
asm """`result` = typeof BigUint64Array != 'undefined'"""
{.emit: """`result` = typeof BigUint64Array != 'undefined';""".}
proc getProtoName*[T](a: T): cstring {.importjs: "Object.prototype.toString.call(#)".} =
runnableExamples:

View File

@@ -763,9 +763,9 @@ proc cmpPaths*(pathA, pathB: string): int {.
## On a case-sensitive filesystem this is done
## case-sensitively otherwise case-insensitively. Returns:
##
## | 0 if pathA == pathB
## | < 0 if pathA < pathB
## | > 0 if pathA > pathB
## | `0` if pathA == pathB
## | `< 0` if pathA < pathB
## | `> 0` if pathA > pathB
runnableExamples:
when defined(macosx):
assert cmpPaths("foo", "Foo") == 0

View File

@@ -66,11 +66,13 @@ proc expandSymlink*(symlinkPath: string): string {.noWeirdTarget.} =
when defined(windows) or defined(nintendoswitch):
result = symlinkPath
else:
result = newString(maxSymlinkLen)
var len = readlink(symlinkPath, result.cstring, maxSymlinkLen)
if len < 0:
raiseOSError(osLastError(), symlinkPath)
if len > maxSymlinkLen:
result = newString(len+1)
len = readlink(symlinkPath, result.cstring, len)
setLen(result, len)
var bufLen = 1024
while true:
result = newString(bufLen)
let len = readlink(symlinkPath.cstring, result.cstring, bufLen)
if len < 0:
raiseOSError(osLastError(), symlinkPath)
if len < bufLen:
result.setLen(len)
break
bufLen = bufLen shl 1

View File

@@ -874,7 +874,7 @@ proc writeFile*(filename: string, content: openArray[byte]) {.since: (1, 1).} =
var f: File = nil
if open(f, filename, fmWrite):
try:
f.writeBuffer(unsafeAddr content[0], content.len)
discard f.writeBuffer(unsafeAddr content[0], content.len)
finally:
close(f)
else:

View File

@@ -110,6 +110,19 @@ template addAllNode(assignParam: NimNode, procParam: NimNode) =
tempAssignList.add newLetStmt(tempNode, newDotExpr(objTemp, formalParams[i][0]))
scratchRecList.add newIdentDefs(newIdentNode(formalParams[i][0].strVal), assignParam)
proc analyseRootSym(s: NimNode): NimNode =
result = s
while true:
case result.kind
of nnkBracketExpr, nnkDerefExpr, nnkHiddenDeref,
nnkAddr, nnkHiddenAddr,
nnkObjDownConv, nnkObjUpConv:
result = result[0]
of nnkDotExpr, nnkCheckedFieldExpr, nnkHiddenStdConv, nnkHiddenSubConv:
result = result[1]
else:
break
macro toTask*(e: typed{nkCall | nkInfix | nkPrefix | nkPostfix | nkCommand | nkCallStrLit}): Task =
## Converts the call and its arguments to `Task`.
runnableExamples:
@@ -121,11 +134,14 @@ macro toTask*(e: typed{nkCall | nkInfix | nkPrefix | nkPostfix | nkCommand | nkC
let retType = getTypeInst(e)
let returnsVoid = retType.typeKind == ntyVoid
let rootSym = analyseRootSym(e[0])
expectKind rootSym, nnkSym
when compileOption("threads"):
if not isGcSafe(e[0]):
if not isGcSafe(rootSym):
error("'toTask' takes a GC safe call expression", e)
if hasClosure(e[0]):
if hasClosure(rootSym):
error("closure call is not allowed", e)
if e.len > 1:
@@ -209,7 +225,7 @@ macro toTask*(e: typed{nkCall | nkInfix | nkPrefix | nkPostfix | nkCommand | nkC
let funcCall = newCall(e[0], callNode)
functionStmtList.add tempAssignList
let funcName = genSym(nskProc, e[0].strVal)
let funcName = genSym(nskProc, rootSym.strVal)
let destroyName = genSym(nskProc, "destroyScratch")
let objTemp2 = genSym(ident = "obj")
let tempNode = quote("@") do:
@@ -241,7 +257,7 @@ macro toTask*(e: typed{nkCall | nkInfix | nkPrefix | nkPostfix | nkCommand | nkC
Task(callback: `funcName`, args: `scratchIdent`, destroy: `destroyName`)
else:
let funcCall = newCall(e[0])
let funcName = genSym(nskProc, e[0].strVal)
let funcName = genSym(nskProc, rootSym.strVal)
if returnsVoid:
result = quote do:

View File

@@ -14,7 +14,7 @@ when defined(nimdoc):
## Wrapper for `time_t`. On posix, this is an alias to `posix.Time`.
elif defined(windows):
when defined(i386) and defined(gcc):
type Time* {.importc: "time_t", header: "<time.h>".} = distinct int32
type Time* {.importc: "time_t", header: "<time.h>".} = distinct clong
else:
# newest version of Visual C++ defines time_t to be of 64 bits
type Time* {.importc: "time_t", header: "<time.h>".} = distinct int64

View File

@@ -82,29 +82,29 @@ proc writeVu64*(z: var openArray[byte], x: uint64): int =
z[3] = cast[uint8](y)
return 4
z[0] = 251
varintWrite32(toOpenArray(z, 1, z.high-1), y)
varintWrite32(toOpenArray(z, 1, 4), y)
return 5
if w <= 255:
z[0] = 252
z[1] = cast[uint8](w)
varintWrite32(toOpenArray(z, 2, z.high-2), y)
varintWrite32(toOpenArray(z, 2, 5), y)
return 6
if w <= 65535:
z[0] = 253
z[1] = cast[uint8](w shr 8)
z[2] = cast[uint8](w)
varintWrite32(toOpenArray(z, 3, z.high-3), y)
varintWrite32(toOpenArray(z, 3, 6), y)
return 7
if w <= 16777215:
z[0] = 254
z[1] = cast[uint8](w shr 16)
z[2] = cast[uint8](w shr 8)
z[3] = cast[uint8](w)
varintWrite32(toOpenArray(z, 4, z.high-4), y)
varintWrite32(toOpenArray(z, 4, 7), y)
return 8
z[0] = 255
varintWrite32(toOpenArray(z, 1, z.high-1), w)
varintWrite32(toOpenArray(z, 5, z.high-5), y)
varintWrite32(toOpenArray(z, 1, 4), w)
varintWrite32(toOpenArray(z, 5, 8), y)
return 9
proc sar(a, b: int64): int64 =

View File

@@ -1037,7 +1037,7 @@ const
## Possible values:
## `"i386"`, `"alpha"`, `"powerpc"`, `"powerpc64"`, `"powerpc64el"`,
## `"sparc"`, `"amd64"`, `"mips"`, `"mipsel"`, `"arm"`, `"arm64"`,
## `"mips64"`, `"mips64el"`, `"riscv32"`, `"riscv64"`, '"loongarch64"'.
## `"mips64"`, `"mips64el"`, `"riscv32"`, `"riscv64"`, `"loongarch64"`.
seqShallowFlag = low(int)
strlitFlag = 1 shl (sizeof(int)*8 - 2) # later versions of the codegen \
@@ -2102,12 +2102,14 @@ when not defined(js):
proc cstringArrayToSeq*(a: cstringArray, len: Natural): seq[string] =
## Converts a `cstringArray` to a `seq[string]`. `a` is supposed to be
## of length `len`.
if a == nil: return @[]
newSeq(result, len)
for i in 0..len-1: result[i] = $a[i]
proc cstringArrayToSeq*(a: cstringArray): seq[string] =
## Converts a `cstringArray` to a `seq[string]`. `a` is supposed to be
## terminated by `nil`.
if a == nil: return @[]
var L = 0
while a[L] != nil: inc(L)
result = cstringArrayToSeq(a, L)
@@ -2788,6 +2790,18 @@ when not defined(js):
proc toOpenArray*[T](x: seq[T]; first, last: int): openArray[T] {.
magic: "Slice".}
## Allows passing the slice of `x` from the element at `first` to the element
## at `last` to `openArray[T]` parameters without copying it.
##
## Example:
## ```nim
## proc test(x: openArray[int]) =
## doAssert x == [1, 2, 3]
##
## let s = @[0, 1, 2, 3, 4]
## s.toOpenArray(1, 3).test
## ```
proc toOpenArray*[T](x: openArray[T]; first, last: int): openArray[T] {.
magic: "Slice".}
proc toOpenArray*[I, T](x: array[I, T]; first, last: I): openArray[T] {.

View File

@@ -20,6 +20,37 @@ template track(op, address, size) =
# We manage *chunks* of memory. Each chunk is a multiple of the page size.
# Each chunk starts at an address that is divisible by the page size.
# Small chunks may be divided into smaller cells of reusable pointers to reduce the number of page allocations.
# An allocation of a small pointer looks approximately like this
#[
alloc -> rawAlloc -> No free chunk available > Request a new page from tslf -> result = chunk.data -------------+
| |
v |
Free chunk available |
| |
v v
Fetch shared cells -> No free cells available -> Advance acc -> result = chunk.data + chunk.acc -------> return
(may not add new cells) ^
| |
v |
Free cells available -> result = chunk.freeList -> Advance chunk.freeList -----------------------------------+
]#
# so it is split into 3 paths, where the last path is preferred to prevent unnecessary allocations.
#
#
# A deallocation of a small pointer then looks like this
#[
dealloc -> rawDealloc -> chunk.owner == addr(a) --------------> This thread owns the chunk ------> The current chunk is active -> Chunk is completely unused -----> Chunk references no foreign cells
| | (Add cell into the current chunk) | Return the current chunk back to tlsf
| | | |
v v v v
A different thread owns this chunk. The current chunk is not active. chunk.free was < size Chunk references foreign cells, noop
Add the cell to a.sharedFreeLists Add the cell into the active chunk Activate the chunk (end)
(end) (end) (end)
]#
# So "true" deallocation is delayed for as long as possible in favor of reusing cells.
const
nimMinHeapPages {.intdefine.} = 128 # 0.5 MB
@@ -71,6 +102,8 @@ const
type
FreeCell {.final, pure.} = object
# A free cell is a pointer that has been freed, meaning it became available for reuse.
# It may become foreign if it is lent to a chunk that did not create it, doing so reduces the amount of needed pages.
next: ptr FreeCell # next free cell in chunk (overlaid with refcount)
when not defined(gcDestructors):
zeroField: int # 0 means cell is not used (overlaid with typ field)
@@ -90,11 +123,18 @@ type
SmallChunk = object of BaseChunk
next, prev: PSmallChunk # chunks of the same size
freeList: ptr FreeCell
free: int # how many bytes remain
acc: int # accumulator for small object allocation
when defined(gcDestructors):
sharedFreeList: ptr FreeCell # make no attempt at avoiding false sharing for now for this object field
freeList: ptr FreeCell # Singly linked list of cells. They may be from foreign chunks or from the current chunk.
# Should be `nil` when the chunk isn't active in `a.freeSmallChunks`.
free: int32 # Bytes this chunk is able to provide using both the accumulator and free cells.
# When a cell is considered foreign, its source chunk's free field is NOT adjusted until it
# reaches dealloc while the source chunk is active.
# Instead, the receiving chunk gains the capacity and thus reserves space in the foreign chunk.
acc: uint32 # Offset from data, used when there are no free cells available but the chunk is considered free.
foreignCells: int # When a free cell is given to a chunk that is not its origin,
# both the cell and the source chunk are considered foreign.
# Receiving a foreign cell can happen both when deallocating from another thread or when
# the active chunk in `a.freeSmallChunks` is not the current chunk.
# Freeing a chunk while `foreignCells > 0` leaks memory as all references to it become lost.
data {.align: MemAlign.}: UncheckedArray[byte] # start of usable memory
BigChunk = object of BaseChunk # not necessarily > PageSize!
@@ -109,7 +149,12 @@ type
MemRegion = object
when not defined(gcDestructors):
minLargeObj, maxLargeObj: int
freeSmallChunks: array[0..max(1,SmallChunkSize div MemAlign-1), PSmallChunk]
freeSmallChunks: array[0..max(1, SmallChunkSize div MemAlign-1), PSmallChunk]
# List of available chunks per size class. Only one is expected to be active per class.
when defined(gcDestructors):
sharedFreeLists: array[0..max(1, SmallChunkSize div MemAlign-1), ptr FreeCell]
# When a thread frees a pointer it did not create, it must not adjust the counters.
# Instead, the cell is placed here and deferred until the next allocation.
flBitmap: uint32
slBitmap: array[RealFli, uint32]
matrix: array[RealFli, array[MaxSli, PBigChunk]]
@@ -433,7 +478,7 @@ iterator allObjects(m: var MemRegion): pointer {.inline.} =
let size = c.size
var a = cast[int](addr(c.data))
let limit = a + c.acc
let limit = a + c.acc.int
while a <% limit:
yield cast[pointer](a)
a = a +% size
@@ -777,41 +822,42 @@ when defined(gcDestructors):
sysAssert c.next == nil, "c.next pointer must be nil"
atomicPrepend a.sharedFreeListBigChunks, c
proc addToSharedFreeList(c: PSmallChunk; f: ptr FreeCell) {.inline.} =
atomicPrepend c.sharedFreeList, f
proc addToSharedFreeList(c: PSmallChunk; f: ptr FreeCell; size: int) {.inline.} =
atomicPrepend c.owner.sharedFreeLists[size], f
const MaxSteps = 20
proc compensateCounters(a: var MemRegion; c: PSmallChunk; size: int) =
# rawDealloc did NOT do the usual:
# `inc(c.free, size); dec(a.occ, size)` because it wasn't the owner of these
# memory locations. We have to compensate here for these for the entire list.
# Well, not for the entire list, but for `max` elements of the list because
# we split the list in order to achieve bounded response times.
var it = c.freeList
var x = 0
var maxIters = 20 # make it time-bounded
var total = 0
while it != nil:
if maxIters == 0:
let rest = it.next.loada
if rest != nil:
it.next.storea nil
addToSharedFreeList(c, rest)
break
inc x, size
it = it.next.loada
dec maxIters
inc(c.free, x)
dec(a.occ, x)
inc total, size
let chunk = cast[PSmallChunk](pageAddr(it))
if c != chunk:
# The cell is foreign, potentially even from a foreign thread.
# It must block the current chunk from being freed, as doing so would leak memory.
inc c.foreignCells
it = it.next
# By not adjusting the foreign chunk we reserve space in it to prevent deallocation
inc(c.free, total)
dec(a.occ, total)
proc freeDeferredObjects(a: var MemRegion; root: PBigChunk) =
var it = root
var maxIters = 20 # make it time-bounded
var maxIters = MaxSteps # make it time-bounded
while true:
let rest = it.next.loada
it.next.storea nil
deallocBigChunk(a, cast[PBigChunk](it))
if maxIters == 0:
let rest = it.next.loada
it.next.storea nil
addToSharedFreeListBigChunks(a, rest)
if rest != nil:
addToSharedFreeListBigChunks(a, rest)
sysAssert a.sharedFreeListBigChunks != nil, "re-enqueing failed"
break
it = it.next.loada
it = rest
dec maxIters
if it == nil: break
@@ -826,56 +872,85 @@ proc rawAlloc(a: var MemRegion, requestedSize: int): pointer =
#c_fprintf(stdout, "alloc; size: %ld; %ld\n", requestedSize, size)
if size <= SmallChunkSize-smallChunkOverhead():
template fetchSharedCells(tc: PSmallChunk) =
# Consumes cells from (potentially) foreign threads from `a.sharedFreeLists[s]`
when defined(gcDestructors):
if tc.freeList == nil:
when hasThreadSupport:
# Steal the entire list from `sharedFreeList`:
tc.freeList = atomicExchangeN(addr a.sharedFreeLists[s], nil, ATOMIC_RELAXED)
else:
tc.freeList = a.sharedFreeLists[s]
a.sharedFreeLists[s] = nil
# if `tc.freeList` isn't nil, `tc` will gain capacity.
# We must calculate how much it gained and how many foreign cells are included.
compensateCounters(a, tc, size)
# allocate a small block: for small chunks, we use only its next pointer
let s = size div MemAlign
var c = a.freeSmallChunks[s]
if c == nil:
# There is no free chunk of the requested size available, we need a new one.
c = getSmallChunk(a)
# init all fields in case memory didn't get zeroed
c.freeList = nil
c.foreignCells = 0
sysAssert c.size == PageSize, "rawAlloc 3"
c.size = size
c.acc = size
when defined(gcDestructors):
c.sharedFreeList = nil
c.free = SmallChunkSize - smallChunkOverhead() - size
c.acc = size.uint32
c.free = SmallChunkSize - smallChunkOverhead() - size.int32
sysAssert c.owner == addr(a), "rawAlloc: No owner set!"
c.next = nil
c.prev = nil
listAdd(a.freeSmallChunks[s], c)
# Shared cells are fetched here in case `c.size * 2 >= SmallChunkSize - smallChunkOverhead()`.
# For those single cell chunks, we would otherwise have to allocate a new one almost every time.
fetchSharedCells(c)
if c.free >= size:
# Because removals from `a.freeSmallChunks[s]` only happen in the other alloc branch and during dealloc,
# we must not add it to the list if it cannot be used the next time a pointer of `size` bytes is needed.
listAdd(a.freeSmallChunks[s], c)
result = addr(c.data)
sysAssert((cast[int](result) and (MemAlign-1)) == 0, "rawAlloc 4")
else:
# There is a free chunk of the requested size available, use it.
sysAssert(allocInv(a), "rawAlloc: begin c != nil")
sysAssert c.next != c, "rawAlloc 5"
#if c.size != size:
# c_fprintf(stdout, "csize: %lld; size %lld\n", c.size, size)
sysAssert c.size == size, "rawAlloc 6"
when defined(gcDestructors):
if c.freeList == nil:
when hasThreadSupport:
c.freeList = atomicExchangeN(addr c.sharedFreeList, nil, ATOMIC_RELAXED)
else:
c.freeList = c.sharedFreeList
c.sharedFreeList = nil
compensateCounters(a, c, size)
if c.freeList == nil:
sysAssert(c.acc + smallChunkOverhead() + size <= SmallChunkSize,
sysAssert(c.acc.int + smallChunkOverhead() + size <= SmallChunkSize,
"rawAlloc 7")
result = cast[pointer](cast[int](addr(c.data)) +% c.acc)
result = cast[pointer](cast[int](addr(c.data)) +% c.acc.int)
inc(c.acc, size)
else:
# There are free cells available, prefer them over the accumulator
result = c.freeList
when not defined(gcDestructors):
sysAssert(c.freeList.zeroField == 0, "rawAlloc 8")
c.freeList = c.freeList.next
if cast[PSmallChunk](pageAddr(result)) != c:
# This cell isn't a blocker for the current chunk's deallocation anymore
dec(c.foreignCells)
else:
sysAssert(c == cast[PSmallChunk](pageAddr(result)), "rawAlloc: Bad cell")
# Even if the cell we return is foreign, the local chunk's capacity decreases.
# The capacity was previously reserved in the source chunk (when it first got allocated),
# then added into the current chunk during dealloc,
# so the source chunk will not be freed or leak memory because of this.
dec(c.free, size)
sysAssert((cast[int](result) and (MemAlign-1)) == 0, "rawAlloc 9")
sysAssert(allocInv(a), "rawAlloc: end c != nil")
sysAssert(allocInv(a), "rawAlloc: before c.free < size")
if c.free < size:
sysAssert(allocInv(a), "rawAlloc: before listRemove test")
listRemove(a.freeSmallChunks[s], c)
sysAssert(allocInv(a), "rawAlloc: end listRemove test")
# We fetch deferred cells *after* advancing `c.freeList`/`acc` to adjust `c.free`.
# If after the adjustment it turns out there's free cells available,
# the chunk stays in `a.freeSmallChunks[s]` and the need for a new chunk is delayed.
fetchSharedCells(c)
sysAssert(allocInv(a), "rawAlloc: before c.free < size")
if c.free < size:
# Even after fetching shared cells the chunk has no usable memory left. It is no longer the active chunk
sysAssert(allocInv(a), "rawAlloc: before listRemove test")
listRemove(a.freeSmallChunks[s], c)
sysAssert(allocInv(a), "rawAlloc: end listRemove test")
sysAssert(((cast[int](result) and PageMask) - smallChunkOverhead()) %%
size == 0, "rawAlloc 21")
sysAssert(allocInv(a), "rawAlloc: end small size")
@@ -907,7 +982,7 @@ proc rawAlloc(a: var MemRegion, requestedSize: int): pointer =
trackSize(c.size)
sysAssert(isAccessible(a, result), "rawAlloc 14")
sysAssert(allocInv(a), "rawAlloc: end")
when logAlloc: cprintf("var pointer_%p = alloc(%ld)\n", result, requestedSize)
when logAlloc: cprintf("var pointer_%p = alloc(%ld) # %p\n", result, requestedSize, addr a)
proc rawAlloc0(a: var MemRegion, requestedSize: int): pointer =
result = rawAlloc(a, requestedSize)
@@ -923,7 +998,7 @@ proc rawDealloc(a: var MemRegion, p: pointer) =
if isSmallChunk(c):
# `p` is within a small chunk:
var c = cast[PSmallChunk](c)
var s = c.size
let s = c.size
# ^ We might access thread foreign storage here.
# The other thread cannot possibly free this block as it's still alive.
var f = cast[ptr FreeCell](p)
@@ -938,31 +1013,48 @@ proc rawDealloc(a: var MemRegion, p: pointer) =
#echo("setting to nil: ", $cast[int](addr(f.zeroField)))
sysAssert(f.zeroField != 0, "rawDealloc 1")
f.zeroField = 0
f.next = c.freeList
c.freeList = f
when overwriteFree:
# set to 0xff to check for usage after free bugs:
nimSetMem(cast[pointer](cast[int](p) +% sizeof(FreeCell)), -1'i32,
s -% sizeof(FreeCell))
# check if it is not in the freeSmallChunks[s] list:
if c.free < s:
# add it to the freeSmallChunks[s] array:
listAdd(a.freeSmallChunks[s div MemAlign], c)
inc(c.free, s)
let activeChunk = a.freeSmallChunks[s div MemAlign]
if activeChunk != nil and c != activeChunk:
# This pointer is not part of the active chunk, lend it out
# and do not adjust the current chunk (same logic as compensateCounters.)
# Put the cell into the active chunk,
# may prevent a queue of available chunks from forming in a.freeSmallChunks[s div MemAlign].
# This queue would otherwise waste memory in the form of free cells until we return to those chunks.
f.next = activeChunk.freeList
activeChunk.freeList = f # lend the cell
inc(activeChunk.free, s) # By not adjusting the current chunk's capacity it is prevented from being freed
inc(activeChunk.foreignCells) # The cell is now considered foreign from the perspective of the active chunk
else:
inc(c.free, s)
if c.free == SmallChunkSize-smallChunkOverhead():
listRemove(a.freeSmallChunks[s div MemAlign], c)
c.size = SmallChunkSize
freeBigChunk(a, cast[PBigChunk](c))
f.next = c.freeList
c.freeList = f
if c.free < s:
# The chunk could not have been active as it didn't have enough space to give
listAdd(a.freeSmallChunks[s div MemAlign], c)
inc(c.free, s)
else:
inc(c.free, s)
# Free only if the entire chunk is unused and there are no borrowed cells.
# If the chunk were to be freed while it references foreign cells,
# the foreign chunks will leak memory and can never be freed.
if c.free == SmallChunkSize-smallChunkOverhead() and c.foreignCells == 0:
listRemove(a.freeSmallChunks[s div MemAlign], c)
c.size = SmallChunkSize
freeBigChunk(a, cast[PBigChunk](c))
else:
when logAlloc: cprintf("dealloc(pointer_%p) # SMALL FROM %p CALLER %p\n", p, c.owner, addr(a))
when defined(gcDestructors):
addToSharedFreeList(c, f)
addToSharedFreeList(c, f, s div MemAlign)
sysAssert(((cast[int](p) and PageMask) - smallChunkOverhead()) %%
s == 0, "rawDealloc 2")
else:
# set to 0xff to check for usage after free bugs:
when overwriteFree: nimSetMem(p, -1'i32, c.size -% bigChunkOverhead())
when logAlloc: cprintf("dealloc(pointer_%p) # BIG %p\n", p, c.owner)
when defined(gcDestructors):
if c.owner == addr(a):
deallocBigChunk(a, cast[PBigChunk](c))
@@ -970,8 +1062,9 @@ proc rawDealloc(a: var MemRegion, p: pointer) =
addToSharedFreeListBigChunks(c.owner[], cast[PBigChunk](c))
else:
deallocBigChunk(a, cast[PBigChunk](c))
sysAssert(allocInv(a), "rawDealloc: end")
when logAlloc: cprintf("dealloc(pointer_%p)\n", p)
#when logAlloc: cprintf("dealloc(pointer_%p)\n", p)
when not defined(gcDestructors):
proc isAllocatedPtr(a: MemRegion, p: pointer): bool =
@@ -982,7 +1075,7 @@ when not defined(gcDestructors):
var c = cast[PSmallChunk](c)
var offset = (cast[int](p) and (PageSize-1)) -%
smallChunkOverhead()
result = (c.acc >% offset) and (offset %% c.size == 0) and
result = (c.acc.int >% offset) and (offset %% c.size == 0) and
(cast[ptr FreeCell](p).zeroField >% 1)
else:
var c = cast[PBigChunk](c)
@@ -1000,7 +1093,7 @@ when not defined(gcDestructors):
var c = cast[PSmallChunk](c)
var offset = (cast[int](p) and (PageSize-1)) -%
smallChunkOverhead()
if c.acc >% offset:
if c.acc.int >% offset:
sysAssert(cast[int](addr(c.data)) +% offset ==
cast[int](p), "offset is not what you think it is")
var d = cast[ptr FreeCell](cast[int](addr(c.data)) +%

View File

@@ -324,7 +324,7 @@ proc `==`*[T](x, y: seq[T]): bool {.noSideEffect.} =
return true
else:
var sameObject = false
{.emit: """`sameObject` = `x` === `y`""".}
{.emit: """`sameObject` = `x` === `y`;""".}
if sameObject: return true
if x.len != y.len:

View File

@@ -10,7 +10,7 @@ const
## is the minor number of Nim's version.
## Odd for devel, even for releases.
NimPatch* {.intdefine.}: int = 1
NimPatch* {.intdefine.}: int = 9
## is the patch number of Nim's version.
## Odd for devel, even for releases.

View File

@@ -80,8 +80,6 @@ proc `[]`*[T, U: Ordinal](s: string, x: HSlice[T, U]): string {.inline, systemRa
## var s = "abcdef"
## assert s[1..3] == "bcd"
## ```
# Workaround bug #22852
result = ""
let a = s ^^ x.a
let L = (s ^^ x.b) - a + 1
result = newString(L)
@@ -112,13 +110,13 @@ proc `[]`*[Idx, T; U, V: Ordinal](a: array[Idx, T], x: HSlice[U, V]): seq[T] {.s
## var a = [1, 2, 3, 4]
## assert a[0..2] == @[1, 2, 3]
## ```
##
## See also:
## * `toOpenArray(array[I, T];I,I) <#toOpenArray,array[I,T],I,I>`_
let xa = a ^^ x.a
let L = (a ^^ x.b) - xa + 1
# Workaround bug #22852:
result = newSeq[T](if L < 0: 0 else: L)
result = newSeq[T](L)
for i in 0..<L: result[i] = a[Idx(i + xa)]
# Workaround bug #22852
discard Natural(L)
proc `[]=`*[Idx, T; U, V: Ordinal](a: var array[Idx, T], x: HSlice[U, V], b: openArray[T]) {.systemRaisesDefect.} =
## Slice assignment for arrays.
@@ -141,6 +139,9 @@ proc `[]`*[T; U, V: Ordinal](s: openArray[T], x: HSlice[U, V]): seq[T] {.systemR
## var s = @[1, 2, 3, 4]
## assert s[0..2] == @[1, 2, 3]
## ```
##
## See also:
## * `toOpenArray(openArray[T];int,int) <#toOpenArray,openArray[T],int,int>`_
let a = s ^^ x.a
let L = (s ^^ x.b) - a + 1
newSeq(result, L)

View File

@@ -622,37 +622,6 @@ proc nimCopy(dest, src: JSRef, ti: PNimType): JSRef =
else:
result = src
proc genericReset(x: JSRef, ti: PNimType): JSRef {.compilerproc.} =
{.emit: "`result` = null;".}
case ti.kind
of tyPtr, tyRef, tyVar, tyNil:
if isFatPointer(ti):
{.emit: """
`result` = [null, 0];
""".}
of tySet:
{.emit: """
`result` = {};
""".}
of tyTuple, tyObject:
if ti.kind == tyObject:
{.emit: "`result` = {m_type: `ti`};".}
else:
{.emit: "`result` = {};".}
of tySequence, tyOpenArray, tyString:
{.emit: """
`result` = [];
""".}
of tyArrayConstr, tyArray:
{.emit: """
`result` = new Array(`x`.length);
for (var i = 0; i < `x`.length; ++i) {
`result`[i] = genericReset(`x`[i], `ti`.base);
}
""".}
else:
discard
proc arrayConstr(len: int, value: JSRef, typ: PNimType): JSRef {.
asmNoStackFrame, compilerproc.} =
# types are fake

View File

@@ -253,11 +253,12 @@ proc cpDir*(`from`, to: string) {.raises: [OSError].} =
proc exec*(command: string) {.
raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =
## Executes an external process. If the external process terminates with
## a non-zero exit code, an OSError exception is raised.
## a non-zero exit code, an OSError exception is raised. The command is
## executed relative to the current source path.
##
## **Note:** If you need a version of `exec` that returns the exit code
## and text output of the command, you can use `system.gorgeEx
## <system.html#gorgeEx,string,string,string>`_.
## .. note:: If you need a version of `exec` that returns the exit code
## and text output of the command, you can use `system.gorgeEx
## <system.html#gorgeEx,string,string,string>`_.
log "exec: " & command:
if rawExec(command) != 0:
raise newException(OSError, "FAILED: " & command)
@@ -267,11 +268,17 @@ proc exec*(command: string, input: string, cache = "") {.
raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =
## Executes an external process. If the external process terminates with
## a non-zero exit code, an OSError exception is raised.
##
## .. warning:: This version of `exec` is executed relative to the nimscript
## module path, which affects how the command resolves relative paths. Thus
## it is generally better to use `gorgeEx` directly when you need more
## control over the execution environment or when working with commands
## that deal with relative paths.
log "exec: " & command:
let (output, exitCode) = gorgeEx(command, input, cache)
echo output
if exitCode != 0:
raise newException(OSError, "FAILED: " & command)
echo output
proc selfExec*(command: string) {.
raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =

View File

@@ -146,7 +146,7 @@ proc unregisterCycle(s: Cell) =
let idx = s.rootIdx-1
when false:
if idx >= roots.len or idx < 0:
cprintf("[Bug!] %ld\n", idx)
cprintf("[Bug!] %ld %ld\n", idx, roots.len)
rawQuit 1
roots.d[idx] = roots.d[roots.len-1]
roots.d[idx][0].rootIdx = idx+1
@@ -303,6 +303,14 @@ proc collectColor(s: Cell; desc: PNimTypeV2; col: int; j: var GcEnv) =
t.setColor(colBlack)
trace(t, desc, j)
const
defaultThreshold = when defined(nimFixedOrc): 10_000 else: 128
when defined(nimStressOrc):
const rootsThreshold = 10 # broken with -d:nimStressOrc: 10 and for havlak iterations 1..8
else:
var rootsThreshold {.threadvar.}: int
proc collectCyclesBacon(j: var GcEnv; lowMark: int) =
# pretty direct translation from
# https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon01Concurrent.pdf
@@ -341,22 +349,25 @@ proc collectCyclesBacon(j: var GcEnv; lowMark: int) =
s.rootIdx = 0
collectColor(s, roots.d[i][1], colToCollect, j)
# Bug #22927: `free` calls destructors which can append to `roots`.
# We protect against this here by setting `roots.len` to 0 and also
# setting the threshold so high that no cycle collection can be triggered
# until we are out of this critical section:
when not defined(nimStressOrc):
let oldThreshold = rootsThreshold
rootsThreshold = high(int)
roots.len = 0
for i in 0 ..< j.toFree.len:
when orcLeakDetector:
writeCell("CYCLIC OBJECT FREED", j.toFree.d[i][0], j.toFree.d[i][1])
free(j.toFree.d[i][0], j.toFree.d[i][1])
when not defined(nimStressOrc):
rootsThreshold = oldThreshold
inc j.freed, j.toFree.len
deinit j.toFree
#roots.len = 0
const
defaultThreshold = when defined(nimFixedOrc): 10_000 else: 128
when defined(nimStressOrc):
const rootsThreshold = 10 # broken with -d:nimStressOrc: 10 and for havlak iterations 1..8
else:
var rootsThreshold {.threadvar.}: int
when defined(nimOrcStats):
var freedCyclicObjects {.threadvar.}: int
@@ -396,7 +407,8 @@ proc collectCycles() =
collectCyclesBacon(j, 0)
deinit j.traceStack
deinit roots
if roots.len == 0:
deinit roots
when not defined(nimStressOrc):
# compute the threshold based on the previous history

View File

@@ -29,7 +29,7 @@ proc reprBool(x: bool): string {.compilerRtl.} =
proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} =
var tmp: bool
let item = typ.node.sons[e]
{.emit: "`tmp` = `item` !== undefined".}
{.emit: "`tmp` = `item` !== undefined;".}
if tmp:
result = makeNimstrLit(item.name)
else:
@@ -136,7 +136,7 @@ proc reprArray(a: pointer, typ: PNimType,
add(result, "]")
proc isPointedToNil(p: pointer): bool =
{. emit: "if (`p` === null) {`result` = true};\n" .}
{. emit: "if (`p` === null) {`result` = true;}\n" .}
proc reprRef(result: var string, p: pointer, typ: PNimType,
cl: var ReprClosure) =

View File

@@ -90,6 +90,12 @@ proc prepareSeqAdd(len: int; p: pointer; addlen, elemSize, elemAlign: int): poin
q.cap = newCap
result = q
proc zeroNewElements(len: int; q: pointer; addlen, elemSize, elemAlign: int) {.
noSideEffect, tags: [], raises: [], compilerRtl.} =
{.noSideEffect.}:
let headerSize = align(sizeof(NimSeqPayloadBase), elemAlign)
zeroMem(q +! headerSize +! len * elemSize, addlen * elemSize)
proc prepareSeqAddUninit(len: int; p: pointer; addlen, elemSize, elemAlign: int): pointer {.
noSideEffect, tags: [], raises: [], compilerRtl.} =
{.noSideEffect.}:

View File

@@ -220,7 +220,10 @@ proc appendChar(dest: NimString, c: char) {.compilerproc, inline.} =
proc setLengthStr(s: NimString, newLen: int): NimString {.compilerRtl.} =
let n = max(newLen, 0)
if s == nil:
result = mnewString(n)
if n == 0:
return s
else:
result = mnewString(n)
elif n <= s.space:
result = s
else:
@@ -301,7 +304,10 @@ proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int): PGenericSeq {.
compilerRtl.} =
sysAssert typ.kind == tySequence, "setLengthSeqV2: type is not a seq"
if s == nil:
result = cast[PGenericSeq](newSeq(typ, newLen))
if newLen == 0:
result = s
else:
result = cast[PGenericSeq](newSeq(typ, newLen))
else:
let elemSize = typ.base.size
let elemAlign = typ.base.align