mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-31 10:53:40 +00:00
Compare commits
1 Commits
pr_temp_in
...
araq-std-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd21306210 |
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@@ -1,11 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
2
.github/workflows/bisects.yml
vendored
2
.github/workflows/bisects.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
name: ${{ matrix.platform }}-bisects
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install OpenSSL (Windows)
|
||||
if: |
|
||||
|
||||
2
.github/workflows/ci_docs.yml
vendored
2
.github/workflows/ci_docs.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
|
||||
8
.github/workflows/ci_packages.yml
vendored
8
.github/workflows/ci_packages.yml
vendored
@@ -33,14 +33,14 @@ jobs:
|
||||
NIM_TESTAMENT_BATCH: ${{ matrix.batch }}
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: 'Install node.js'
|
||||
uses: actions/setup-node@v6
|
||||
- name: 'Install node.js 20.x'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
node-version: '20.x'
|
||||
|
||||
- name: 'Install dependencies (Linux amd64)'
|
||||
if: runner.os == 'Linux' && matrix.cpu == 'amd64'
|
||||
|
||||
8
.github/workflows/ci_publish.yml
vendored
8
.github/workflows/ci_publish.yml
vendored
@@ -17,14 +17,14 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: 'Install node.js'
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
node-version: ''
|
||||
|
||||
- name: 'Install dependencies (Linux amd64)'
|
||||
if: runner.os == 'Linux' && matrix.cpu == 'amd64'
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
run: nim c -r -d:release ci/action.nim
|
||||
|
||||
- name: 'Comment'
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
days-before-pr-stale: 365
|
||||
days-before-pr-close: 30
|
||||
|
||||
@@ -33,7 +33,7 @@ errors.
|
||||
|
||||
- Bitshift operators (`shl`, `shr`, `ashr`) now apply bitmasking to the right operand in the C/C++/VM/JS backends.
|
||||
|
||||
- Adds a new warning `--warning:ImplicitRangeConversion` that detects downsizing implicit conversions to range types (e.g., `int -> range[0..255]` or `range[1..256] -> range[0..255]`) that could cause runtime panics. Safe conversions like `range[0..255] -> range[0..65535]` and explicit casts do not trigger warnings. `int` to `Natural` and `Positive` conversions do not trigger warnings, which can be enabled with `--warning:systemRangeConversion`.
|
||||
- Adds a new warning enabled by `--warning:ImplicitRangeConversion` that detects downsizing implicit conversions to range types (e.g., `int -> range[0..255]` or `range[1..256] -> range[0..255]`) that could cause runtime panics. Safe conversions like `range[0..255] -> range[0..65535]` and explicit casts are not warned on.
|
||||
|
||||
## Standard library additions and changes
|
||||
|
||||
@@ -61,10 +61,6 @@ errors.
|
||||
|
||||
- `system.setLenUninit` now supports refc, JS and VM backends.
|
||||
|
||||
- `std/parseopt` now supports multiple parser modes via a `CliMode` enum.
|
||||
Modes include `Nim` (default, fully compatible) and two new experimental modes:
|
||||
`Lax` and `Gnu` for different option parsing behaviors.
|
||||
|
||||
[//]: # "Changes:"
|
||||
|
||||
- `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type.
|
||||
|
||||
@@ -202,11 +202,7 @@ type
|
||||
tySequence,
|
||||
tyProc,
|
||||
tyPointer, tyOpenArray,
|
||||
tyString, tyCstring,
|
||||
tyForward,
|
||||
# a type not yet semchecked
|
||||
# When semcheck a type section, all types defined in it are initialized to tyForward
|
||||
|
||||
tyString, tyCstring, tyForward,
|
||||
tyInt, tyInt8, tyInt16, tyInt32, tyInt64, # signed integers
|
||||
tyFloat, tyFloat32, tyFloat64, tyFloat128,
|
||||
tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64,
|
||||
|
||||
@@ -69,7 +69,7 @@ proc copyHalf[Key, Val](h, result: Node[Key, Val]) =
|
||||
result.links[j] = h.links[Mhalf + j]
|
||||
else:
|
||||
for j in 0..<Mhalf:
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
result.vals[j] = move h.vals[Mhalf + j]
|
||||
else:
|
||||
shallowCopy(result.vals[j], h.vals[Mhalf + j])
|
||||
@@ -92,7 +92,7 @@ proc insert[Key, Val](h: Node[Key, Val], key: Key, val: Val): Node[Key, Val] =
|
||||
if less(key, h.keys[j]): break
|
||||
inc j
|
||||
for i in countdown(h.entries, j+1):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
h.vals[i] = move h.vals[i-1]
|
||||
else:
|
||||
shallowCopy(h.vals[i], h.vals[i-1])
|
||||
|
||||
@@ -331,7 +331,7 @@ proc withTmpIfNeeded(p: BProc, a: TLoc, needsTmp: bool): TLoc =
|
||||
# Bug https://github.com/status-im/nimbus-eth2/issues/1549
|
||||
# Aliasing is preferred over stack overflows.
|
||||
# Also don't regress for non ARC-builds, too risky.
|
||||
if needsTmp and a.lode.typ != nil and p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc} and
|
||||
if needsTmp and a.lode.typ != nil and p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc} and
|
||||
getSize(p.config, a.lode.typ) < 1024:
|
||||
result = getTemp(p, a.lode.typ, needsInit=false)
|
||||
genAssignment(p, result, a, {})
|
||||
|
||||
@@ -416,7 +416,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
|
||||
else:
|
||||
simpleAsgn(p.s(cpsStmts), dest, src)
|
||||
of tyArray:
|
||||
if containsGarbageCollectedRef(dest.t) and p.config.selectedGC notin {gcArc, gcAtomicArc, gcOrc, gcYrc, gcHooks}:
|
||||
if containsGarbageCollectedRef(dest.t) and p.config.selectedGC notin {gcArc, gcAtomicArc, gcOrc, gcHooks}:
|
||||
genGenericAsgn(p, dest, src, flags)
|
||||
else:
|
||||
let rd = rdLoc(dest)
|
||||
@@ -1832,7 +1832,7 @@ proc genObjConstr(p: BProc, e: PNode, d: var TLoc) =
|
||||
|
||||
var tmp: TLoc = default(TLoc)
|
||||
var r: Rope
|
||||
let needsZeroMem = p.config.selectedGC notin {gcArc, gcAtomicArc, gcOrc, gcYrc} or nfAllFieldsSet notin e.flags
|
||||
let needsZeroMem = p.config.selectedGC notin {gcArc, gcAtomicArc, gcOrc} or nfAllFieldsSet notin e.flags
|
||||
if useTemp:
|
||||
tmp = getTemp(p, t)
|
||||
r = rdLoc(tmp)
|
||||
@@ -2751,7 +2751,7 @@ proc genMove(p: BProc; n: PNode; d: var TLoc) =
|
||||
p.s(cpsStmts).addFieldAssignment(destVal, "p", dotField(srcVal, "p"))
|
||||
else:
|
||||
if d.k == locNone: d = getTemp(p, n.typ)
|
||||
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}:
|
||||
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}:
|
||||
genAssignment(p, d, a, {})
|
||||
var op = getAttachedOp(p.module.g.graph, n.typ, attachedWasMoved)
|
||||
if op == nil:
|
||||
@@ -2835,7 +2835,7 @@ proc genSlice(p: BProc; e: PNode; d: var TLoc) =
|
||||
let (x, y) = genOpenArraySlice(p, e, e.typ, e.typ.elementType,
|
||||
prepareForMutation = e[1].kind == nkHiddenDeref and
|
||||
e[1].typ.skipTypes(abstractInst).kind == tyString and
|
||||
p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc})
|
||||
p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc})
|
||||
if d.k == locNone: d = getTemp(p, e.typ)
|
||||
let dest = rdLoc(d)
|
||||
p.s(cpsStmts).addFieldAssignment(dest, "Field0", x)
|
||||
@@ -3039,7 +3039,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
|
||||
let n = semparallel.liftParallel(p.module.g.graph, p.module.idgen, p.module.module, e)
|
||||
expr(p, n, d)
|
||||
of mDeepCopy:
|
||||
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc} and optEnableDeepCopy notin p.config.globalOptions:
|
||||
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc} and optEnableDeepCopy notin p.config.globalOptions:
|
||||
localError(p.config, e.info,
|
||||
"for --mm:arc|atomicArc|orc 'deepcopy' support has to be enabled with --deepcopy:on")
|
||||
|
||||
@@ -3271,11 +3271,7 @@ proc upConv(p: BProc, n: PNode, d: var TLoc) =
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseObjectConversionError"))
|
||||
raiseInstr(p, p.s(cpsStmts))
|
||||
|
||||
# skip cast when types map to the same C type
|
||||
# this avoids invalid C code like `*(T*)&x` for types that can't have their address taken (e.g., WASM __externref_t)
|
||||
if getTypeDesc(p.module, n.typ) == getTypeDesc(p.module, n[0].typ):
|
||||
expr(p, n[0], d)
|
||||
elif n[0].typ.kind != tyObject:
|
||||
if n[0].typ.kind != tyObject:
|
||||
let destTyp = getTypeDesc(p.module, n.typ)
|
||||
let val = rdLoc(a)
|
||||
if n.isLValue:
|
||||
@@ -3321,7 +3317,7 @@ proc downConv(p: BProc, n: PNode, d: var TLoc) =
|
||||
cCast(ptrType(destType),
|
||||
wrapPar(cAddr(wrapPar(val))))),
|
||||
a.storage)
|
||||
elif p.module.compileToCpp or isImportedType(src):
|
||||
elif p.module.compileToCpp:
|
||||
# C++ implicitly downcasts for us
|
||||
expr(p, arg, d)
|
||||
else:
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
## implementation.
|
||||
|
||||
template detectVersion(field, corename) =
|
||||
if m.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc, gcHooks}:
|
||||
if m.g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcHooks}:
|
||||
result = 2
|
||||
else:
|
||||
result = 1
|
||||
|
||||
@@ -277,7 +277,7 @@ proc isInvalidReturnType(conf: ConfigRef; typ: PType, isProc = true): bool =
|
||||
of ctStruct:
|
||||
let t = skipTypes(rettype, typedescInst)
|
||||
if rettype.isImportedCppType or t.isImportedCppType or
|
||||
(typ.callConv == ccCDecl and conf.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}):
|
||||
(typ.callConv == ccCDecl and conf.selectedGC in {gcArc, gcAtomicArc, gcOrc}):
|
||||
# prevents nrvo for cdecl procs; # bug #23401
|
||||
result = false
|
||||
else:
|
||||
@@ -1692,7 +1692,7 @@ proc genHook(m: BModule; t: PType; info: TLineInfo; op: TTypeAttachedOp; result:
|
||||
echo "ayclic but has this =trace ", t, " ", theProc.ast
|
||||
else:
|
||||
when false:
|
||||
if op == attachedTrace and m.config.selectedGC in {gcOrc, gcYrc} and
|
||||
if op == attachedTrace and m.config.selectedGC == gcOrc and
|
||||
containsGarbageCollectedRef(t):
|
||||
# unfortunately this check is wrong for an object type that only contains
|
||||
# .cursor fields like 'Node' inside 'cycleleak'.
|
||||
|
||||
@@ -1332,7 +1332,7 @@ proc genProcLvl3*(m: BModule, prc: PSym) =
|
||||
# declare the result symbol:
|
||||
assignLocalVar(p, resNode)
|
||||
assert(res.loc.snippet != "")
|
||||
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc} and
|
||||
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc} and
|
||||
allPathsAsgnResult(p, procBody) == InitSkippable:
|
||||
# In an ideal world the codegen could rely on injectdestructors doing its job properly
|
||||
# and then the analysis step would not be required.
|
||||
@@ -1687,7 +1687,7 @@ proc hcrGetProcLoadCode(builder: var Builder, m: BModule, sym, prefix, handle, g
|
||||
# prevents inlining of the NimMainInner function and dependent
|
||||
# functions, which might otherwise merge their stack frames.
|
||||
proc isInnerMainVolatile(m: BModule): bool =
|
||||
m.config.selectedGC notin {gcNone, gcArc, gcAtomicArc, gcOrc, gcYrc}
|
||||
m.config.selectedGC notin {gcNone, gcArc, gcAtomicArc, gcOrc}
|
||||
|
||||
proc genPreMain(m: BModule) =
|
||||
m.s[cfsProcs].addDeclWithVisibility(Private):
|
||||
@@ -1699,6 +1699,8 @@ proc genPreMain(m: BModule) =
|
||||
m.s[cfsProcs].addVar(name = "cmdCount", typ = CInt)
|
||||
m.s[cfsProcs].addDeclWithVisibility(Private):
|
||||
m.s[cfsProcs].addVar(name = "cmdLine", typ = ptrType(ptrType(CChar)))
|
||||
m.s[cfsProcs].addDeclWithVisibility(Private):
|
||||
m.s[cfsProcs].addVar(name = "gEnv", typ = ptrType(ptrType(CChar)))
|
||||
m.s[cfsProcs].addDeclWithVisibility(Private):
|
||||
m.s[cfsProcs].addProcHeader(m.config.nimMainPrefix & "PreMain", CVoid, cProcParams())
|
||||
m.s[cfsProcs].finishProcHeaderWithBody():
|
||||
@@ -1732,7 +1734,7 @@ proc genNimMainInner(m: BModule) =
|
||||
m.s[cfsProcs].addNewline()
|
||||
|
||||
proc initStackBottom(m: BModule): bool =
|
||||
not (m.config.target.targetOS == osStandalone or m.config.selectedGC in {gcNone, gcArc, gcAtomicArc, gcOrc, gcYrc})
|
||||
not (m.config.target.targetOS == osStandalone or m.config.selectedGC in {gcNone, gcArc, gcAtomicArc, gcOrc})
|
||||
|
||||
proc genNimMainProc(m: BModule, preMainCode: Snippet) =
|
||||
m.s[cfsProcs].addProcHeader(ccCDecl, m.config.nimMainPrefix & "NimMain", CVoid, cProcParams())
|
||||
@@ -1759,10 +1761,12 @@ proc genNimMainBody(m: BModule, preMainCode: Snippet) =
|
||||
proc genPosixCMain(m: BModule) =
|
||||
m.s[cfsProcs].addProcHeader("main", CInt, cProcParams(
|
||||
(name: "argc", typ: CInt),
|
||||
(name: "args", typ: ptrType(ptrType(CChar)))))
|
||||
(name: "args", typ: ptrType(ptrType(CChar))),
|
||||
(name: "env", typ: ptrType(ptrType(CChar)))))
|
||||
m.s[cfsProcs].finishProcHeaderWithBody():
|
||||
m.s[cfsProcs].addAssignment("cmdLine", "args")
|
||||
m.s[cfsProcs].addAssignment("cmdCount", "argc")
|
||||
m.s[cfsProcs].addAssignment("gEnv", "env")
|
||||
genMainProcsWithResult(m)
|
||||
m.s[cfsProcs].addNewline()
|
||||
|
||||
@@ -1860,7 +1864,7 @@ proc genMainProc(m: BModule) =
|
||||
builder.addCallStmt(cgsymValue(m, "nimLoadLibraryError"), strLit)
|
||||
|
||||
loadLib(preMainBuilder, "hcr_handle", "hcrGetProc")
|
||||
if m.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}:
|
||||
if m.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}:
|
||||
preMainBuilder.addCallStmt(m.config.nimMainPrefix & "PreMain")
|
||||
else:
|
||||
preMainBuilder.addVar(name = "rtl_handle", typ = CPointer)
|
||||
@@ -2030,7 +2034,7 @@ proc registerModuleToMain(g: BModuleList; m: BModule) =
|
||||
if sfSystemModule in m.module.flags:
|
||||
if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone:
|
||||
g.mainDatInit.addCallStmt(cgsymValue(m, "initThreadVarsEmulation"))
|
||||
if m.config.target.targetOS != osStandalone and m.config.selectedGC notin {gcNone, gcArc, gcAtomicArc, gcOrc, gcYrc}:
|
||||
if m.config.target.targetOS != osStandalone and m.config.selectedGC notin {gcNone, gcArc, gcAtomicArc, gcOrc}:
|
||||
g.mainDatInit.addCallStmt(cgsymValue(m, "initStackBottomWith"),
|
||||
cCast(CPointer, cAddr("inner")))
|
||||
|
||||
@@ -2599,7 +2603,7 @@ proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode) =
|
||||
cgsym(m, "rawWrite")
|
||||
|
||||
# raise dependencies on behalf of genMainProc
|
||||
if m.config.target.targetOS != osStandalone and m.config.selectedGC notin {gcNone, gcArc, gcAtomicArc, gcOrc, gcYrc}:
|
||||
if m.config.target.targetOS != osStandalone and m.config.selectedGC notin {gcNone, gcArc, gcAtomicArc, gcOrc}:
|
||||
cgsym(m, "initStackBottomWith")
|
||||
if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone:
|
||||
cgsym(m, "initThreadVarsEmulation")
|
||||
@@ -2607,7 +2611,7 @@ proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode) =
|
||||
if m.g.forwardedProcs.len == 0:
|
||||
incl m.flags, objHasKidsValid
|
||||
if optMultiMethods in m.g.config.globalOptions or
|
||||
m.g.config.selectedGC notin {gcArc, gcOrc, gcAtomicArc, gcYrc} or
|
||||
m.g.config.selectedGC notin {gcArc, gcOrc, gcAtomicArc} or
|
||||
vtables notin m.g.config.features:
|
||||
generateIfMethodDispatchers(graph, m.idgen)
|
||||
|
||||
|
||||
@@ -727,7 +727,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
|
||||
n[0] = ex
|
||||
result.add(n)
|
||||
|
||||
of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv, nkObjDownConv, nkObjUpConv,
|
||||
of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv, nkObjDownConv,
|
||||
nkDerefExpr, nkHiddenDeref:
|
||||
var ns = false
|
||||
for i in ord(n.kind == nkCast)..<n.len:
|
||||
|
||||
@@ -245,7 +245,7 @@ proc processCompile(conf: ConfigRef; filename: string) =
|
||||
extccomp.addExternalFileToCompile(conf, found)
|
||||
|
||||
const
|
||||
errNoneBoehmRefcExpectedButXFound = "'arc', 'orc', 'yrc', 'atomicArc', 'markAndSweep', 'boehm', 'go', 'none', 'regions', or 'refc' expected, but '$1' found"
|
||||
errNoneBoehmRefcExpectedButXFound = "'arc', 'orc', 'atomicArc', 'markAndSweep', 'boehm', 'go', 'none', 'regions', or 'refc' expected, but '$1' found"
|
||||
errNoneSpeedOrSizeExpectedButXFound = "'none', 'speed' or 'size' expected, but '$1' found"
|
||||
errGuiConsoleOrLibExpectedButXFound = "'gui', 'console', 'lib' or 'staticlib' expected, but '$1' found"
|
||||
errInvalidExceptionSystem = "'goto', 'setjmp', 'cpp' or 'quirky' expected, but '$1' found"
|
||||
@@ -266,7 +266,6 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
|
||||
of "markandsweep": result = conf.selectedGC == gcMarkAndSweep
|
||||
of "destructors", "arc": result = conf.selectedGC == gcArc
|
||||
of "orc": result = conf.selectedGC == gcOrc
|
||||
of "yrc": result = conf.selectedGC == gcYrc
|
||||
of "hooks": result = conf.selectedGC == gcHooks
|
||||
of "go": result = conf.selectedGC == gcGo
|
||||
of "none": result = conf.selectedGC == gcNone
|
||||
@@ -571,7 +570,6 @@ proc unregisterArcOrc*(conf: ConfigRef) =
|
||||
undefSymbol(conf.symbols, "gcdestructors")
|
||||
undefSymbol(conf.symbols, "gcarc")
|
||||
undefSymbol(conf.symbols, "gcorc")
|
||||
undefSymbol(conf.symbols, "gcyrc")
|
||||
undefSymbol(conf.symbols, "gcatomicarc")
|
||||
undefSymbol(conf.symbols, "nimSeqsV2")
|
||||
undefSymbol(conf.symbols, "nimV2")
|
||||
@@ -605,10 +603,6 @@ proc processMemoryManagementOption(switch, arg: string, pass: TCmdLinePass,
|
||||
conf.selectedGC = gcOrc
|
||||
defineSymbol(conf.symbols, "gcorc")
|
||||
registerArcOrc(pass, conf)
|
||||
of "yrc":
|
||||
conf.selectedGC = gcYrc
|
||||
defineSymbol(conf.symbols, "gcyrc")
|
||||
registerArcOrc(pass, conf)
|
||||
of "atomicarc":
|
||||
conf.selectedGC = gcAtomicArc
|
||||
defineSymbol(conf.symbols, "gcatomicarc")
|
||||
|
||||
@@ -483,7 +483,7 @@ proc constructCfg*(s: PSym; body: PNode; root: PSym): ControlFlowGraph =
|
||||
gen(c, body)
|
||||
if root.kind == skResult:
|
||||
genImplicitReturn(c)
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
result = c.code # will move
|
||||
else:
|
||||
shallowCopy(result, c.code)
|
||||
|
||||
@@ -69,7 +69,7 @@ proc hasDestructor(c: Con; t: PType): bool {.inline.} =
|
||||
result = ast.hasDestructor(t)
|
||||
when toDebug.len > 0:
|
||||
# for more effective debugging
|
||||
if not result and c.graph.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
if not result and c.graph.config.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
|
||||
assert(not containsGarbageCollectedRef(t))
|
||||
|
||||
proc getTemp(c: var Con; s: var Scope; typ: PType; info: TLineInfo): PNode =
|
||||
@@ -165,7 +165,7 @@ proc isLastReadImpl(n: PNode; c: var Con; scope: var Scope): bool =
|
||||
|
||||
template hasDestructorOrAsgn(c: var Con, typ: PType): bool =
|
||||
# bug #23354; an object type could have a non-trivial assignements when it is passed to a sink parameter
|
||||
hasDestructor(c, typ) or (c.graph.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc} and
|
||||
hasDestructor(c, typ) or (c.graph.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
|
||||
typ.kind == tyObject and not isTrivial(getAttachedOp(c.graph, typ, attachedAsgn)))
|
||||
|
||||
proc isLastRead(n: PNode; c: var Con; s: var Scope): bool =
|
||||
@@ -329,14 +329,14 @@ proc isCriticalLink(dest: PNode): bool {.inline.} =
|
||||
result = dest.kind != nkSym
|
||||
|
||||
proc finishCopy(c: var Con; result, dest: PNode; flags: set[MoveOrCopyFlag]; isFromSink: bool) =
|
||||
if c.graph.config.selectedGC in {gcOrc, gcYrc} and IsExplicitSink notin flags:
|
||||
if c.graph.config.selectedGC == gcOrc and IsExplicitSink notin flags:
|
||||
# add cyclic flag, but not to sink calls, which IsExplicitSink generates
|
||||
let t = dest.typ.skipTypes(tyUserTypeClasses + {tyGenericInst, tyAlias, tySink, tyDistinct})
|
||||
if cyclicType(c.graph, t):
|
||||
result.add boolLit(c.graph, result.info, isFromSink or isCriticalLink(dest))
|
||||
|
||||
proc genMarkCyclic(c: var Con; result, dest: PNode) =
|
||||
if c.graph.config.selectedGC in {gcOrc, gcYrc}:
|
||||
if c.graph.config.selectedGC == gcOrc:
|
||||
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct})
|
||||
if cyclicType(c.graph, t):
|
||||
if t.kind == tyRef:
|
||||
@@ -457,10 +457,10 @@ proc isCapturedVar(n: PNode): bool =
|
||||
else: result = false
|
||||
|
||||
proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode =
|
||||
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
||||
let nTyp = n.typ.skipTypes(tyUserTypeClasses)
|
||||
let tmp = c.getTemp(s, nTyp, n.info)
|
||||
if hasDestructorOrAsgn(c, nTyp):
|
||||
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
||||
let tmp = c.getTemp(s, nTyp, n.info)
|
||||
let typ = nTyp.skipTypes({tyGenericInst, tyAlias, tySink})
|
||||
let op = getAttachedOp(c.graph, typ, attachedDup)
|
||||
if op != nil and tfHasOwned notin typ.flags:
|
||||
@@ -494,15 +494,15 @@ proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode =
|
||||
if c.inEnsureMove > 0:
|
||||
localError(c.graph.config, n.info, errFailedMove,
|
||||
("cannot move '$1', passing '$1' to a sink parameter introduces an implicit copy") % $n)
|
||||
# Since we know somebody will take over the produced copy, there is
|
||||
# no need to destroy it.
|
||||
result.add tmp
|
||||
else:
|
||||
if c.graph.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
if c.graph.config.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
|
||||
assert(not containsManagedMemory(nTyp))
|
||||
if nTyp.skipTypes(abstractInst).kind in {tyOpenArray, tyVarargs}:
|
||||
localError(c.graph.config, n.info, "cannot create an implicit openArray copy to be passed to a sink parameter")
|
||||
result = p(n, c, s, normal)
|
||||
result.add newTree(nkAsgn, tmp, p(n, c, s, normal))
|
||||
# Since we know somebody will take over the produced copy, there is
|
||||
# no need to destroy it.
|
||||
result.add tmp
|
||||
|
||||
proc isDangerousSeq(t: PType): bool {.inline.} =
|
||||
let t = t.skipTypes(abstractInst)
|
||||
@@ -926,7 +926,7 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
|
||||
|
||||
if n[0].kind == nkSym and n[0].sym.magic in {mNew, mNewFinalize}:
|
||||
result[0] = copyTree(n[0])
|
||||
if c.graph.config.selectedGC in {gcHooks, gcArc, gcAtomicArc, gcOrc, gcYrc}:
|
||||
if c.graph.config.selectedGC in {gcHooks, gcArc, gcAtomicArc, gcOrc}:
|
||||
let destroyOld = c.genDestroy(result[1])
|
||||
result = newTree(nkStmtList, destroyOld, result)
|
||||
else:
|
||||
|
||||
@@ -163,7 +163,7 @@ proc fillBodyObj(c: var TLiftCtx; n, body, x, y: PNode; enforceDefaultOp: bool,
|
||||
if c.filterDiscriminator != nil: return
|
||||
let f = n.sym
|
||||
let b = if c.kind == attachedTrace: y else: y.dotField(f)
|
||||
if (sfCursor in f.flags and c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc, gcHooks}) or
|
||||
if (sfCursor in f.flags and c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcHooks}) or
|
||||
enforceDefaultOp:
|
||||
defaultOp(c, f.typ, body, x.dotField(f), b)
|
||||
else:
|
||||
@@ -558,22 +558,6 @@ proc declareTempOf(c: var TLiftCtx; body: PNode; value: PNode): PNode =
|
||||
v.addVar(result, value)
|
||||
body.add v
|
||||
|
||||
proc considerInferDupFromCopy(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
|
||||
## For `=dup`, if no explicit hook exists, try to infer from `=copy` hook
|
||||
## to maintain backward compatibility. Returns true if inference was applied.
|
||||
if c.kind == attachedDup:
|
||||
var op2 = getAttachedOp(c.g, t, attachedAsgn)
|
||||
if op2 != nil and sfOverridden in op2.flags:
|
||||
#markUsed(c.g.config, c.info, op, c.g.usageSym)
|
||||
onUse(c.info, op2)
|
||||
body.add genBuiltin(c, mWasMoved, "wasMoved", x)
|
||||
body.add newHookCall(c, op2, x, y)
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc addIncStmt(c: var TLiftCtx; body, i: PNode) =
|
||||
let incCall = genBuiltin(c, mInc, "inc", i)
|
||||
incCall.add lowerings.newIntLit(c.g, c.info, 1)
|
||||
@@ -737,43 +721,14 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
dest[] = source
|
||||
decRef tmp
|
||||
|
||||
For YRC the write barrier is more complicated still and must be:
|
||||
|
||||
let tmp = dest
|
||||
# assignment must come first so that the collector sees the most-recent graph:
|
||||
atomic: dest[] = source
|
||||
# Then teach the cycle collector about the changes edge (these use locks, see yrc.nim):
|
||||
incRef source
|
||||
decRef tmp
|
||||
|
||||
This is implemented as a single runtime call (nimAsgnYrc / nimSinkYrc).
|
||||
]#
|
||||
var actions = newNodeI(nkStmtList, c.info)
|
||||
let elemType = t.elementType
|
||||
|
||||
createTypeBoundOps(c.g, c.c, elemType, c.info, c.idgen)
|
||||
let isCyclic = c.g.config.selectedGC == gcOrc and types.canFormAcycle(c.g, elemType)
|
||||
|
||||
# YRC uses dedicated runtime procs for the entire write barrier:
|
||||
if c.g.config.selectedGC == gcYrc:
|
||||
let desc =
|
||||
if isFinal(elemType):
|
||||
let ti = genBuiltin(c, mGetTypeInfoV2, "getTypeInfoV2", newNodeIT(nkType, x.info, elemType))
|
||||
ti.typ = getSysType(c.g, c.info, tyPointer)
|
||||
ti
|
||||
else:
|
||||
newNodeIT(nkNilLit, c.info, getSysType(c.g, c.info, tyPointer))
|
||||
case c.kind
|
||||
of attachedAsgn, attachedDup:
|
||||
body.add callCodegenProc(c.g, "nimAsgnYrc", c.info, genAddr(c, x), y, desc)
|
||||
return
|
||||
of attachedSink:
|
||||
body.add callCodegenProc(c.g, "nimSinkYrc", c.info, genAddr(c, x), y, desc)
|
||||
return
|
||||
else: discard # fall through for destructor, trace, wasMoved
|
||||
|
||||
let isCyclic = c.g.config.selectedGC in {gcOrc, gcYrc} and types.canFormAcycle(c.g, elemType)
|
||||
|
||||
let isInheritableAcyclicRef = c.g.config.selectedGC in {gcOrc, gcYrc} and
|
||||
let isInheritableAcyclicRef = c.g.config.selectedGC == gcOrc and
|
||||
(not isPureObject(elemType)) and
|
||||
tfAcyclic in skipTypes(elemType, abstractInst+{tyOwned}-{tyTypeDesc}).flags
|
||||
# dynamic Acyclic refs need to use dyn decRef
|
||||
@@ -855,26 +810,7 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
let xenv = genBuiltin(c, mAccessEnv, "accessEnv", x)
|
||||
xenv.typ = getSysType(c.g, c.info, tyPointer)
|
||||
|
||||
# Closures are (fnPtr, env) pairs. nimAsgnYrc/nimSinkYrc handle the env pointer
|
||||
# (atomic store + buffered inc/dec). We also need newAsgnStmt to copy the fnPtr.
|
||||
if c.g.config.selectedGC == gcYrc:
|
||||
let nilDesc = newNodeIT(nkNilLit, c.info, getSysType(c.g, c.info, tyPointer))
|
||||
let yenv = genBuiltin(c, mAccessEnv, "accessEnv", y)
|
||||
yenv.typ = getSysType(c.g, c.info, tyPointer)
|
||||
case c.kind
|
||||
of attachedAsgn, attachedDup:
|
||||
# nimAsgnYrc: save old env, atomic store new env, inc new env, dec old env
|
||||
body.add callCodegenProc(c.g, "nimAsgnYrc", c.info, genAddr(c, xenv), yenv, nilDesc)
|
||||
# Raw struct copy to also update the function pointer (env write is redundant but benign)
|
||||
body.add newAsgnStmt(x, y)
|
||||
return
|
||||
of attachedSink:
|
||||
body.add callCodegenProc(c.g, "nimSinkYrc", c.info, genAddr(c, xenv), yenv, nilDesc)
|
||||
body.add newAsgnStmt(x, y)
|
||||
return
|
||||
else: discard # fall through for destructor, trace, wasMoved
|
||||
|
||||
let isCyclic = c.g.config.selectedGC in {gcOrc, gcYrc}
|
||||
let isCyclic = c.g.config.selectedGC == gcOrc
|
||||
let tmp =
|
||||
if isCyclic and c.kind in {attachedAsgn, attachedSink, attachedDup}:
|
||||
declareTempOf(c, body, xenv)
|
||||
@@ -907,6 +843,7 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
body.add genIf(c, cond, actions)
|
||||
else:
|
||||
body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRef", c.info, yenv))
|
||||
|
||||
body.add genIf(c, cond, actions)
|
||||
body.add newAsgnStmt(x, y)
|
||||
of attachedDup:
|
||||
@@ -991,7 +928,7 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
call[1] = y
|
||||
body.add newAsgnStmt(x, call)
|
||||
elif (optOwnedRefs in c.g.config.globalOptions and
|
||||
optRefCheck in c.g.config.options) or c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}:
|
||||
optRefCheck in c.g.config.options) or c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}:
|
||||
let xx = genBuiltin(c, mAccessEnv, "accessEnv", x)
|
||||
xx.typ = getSysType(c.g, c.info, tyPointer)
|
||||
case c.kind
|
||||
@@ -1046,7 +983,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
tyPtr, tyUncheckedArray, tyVar, tyLent:
|
||||
defaultOp(c, t, body, x, y)
|
||||
of tyRef:
|
||||
if c.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
if c.g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
|
||||
atomicRefOp(c, t, body, x, y)
|
||||
elif (optOwnedRefs in c.g.config.globalOptions and
|
||||
optRefCheck in c.g.config.options):
|
||||
@@ -1055,7 +992,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
defaultOp(c, t, body, x, y)
|
||||
of tyProc:
|
||||
if t.callConv == ccClosure:
|
||||
if c.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
if c.g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
|
||||
atomicClosureOp(c, t, body, x, y)
|
||||
else:
|
||||
closureOp(c, t, body, x, y)
|
||||
@@ -1116,12 +1053,19 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
elif tfUnion in t.flags: # bug #25236
|
||||
defaultOp(c, t, body, x, y)
|
||||
else:
|
||||
if not considerInferDupFromCopy(c, t, body, x, y):
|
||||
if c.kind == attachedDup:
|
||||
var op2 = getAttachedOp(c.g, t, attachedAsgn)
|
||||
if op2 != nil and sfOverridden in op2.flags:
|
||||
#markUsed(c.g.config, c.info, op, c.g.usageSym)
|
||||
onUse(c.info, op2)
|
||||
body.add newHookCall(c, t.assignment, x, y)
|
||||
else:
|
||||
fillBodyObjT(c, t, body, x, y)
|
||||
else:
|
||||
fillBodyObjT(c, t, body, x, y)
|
||||
of tyDistinct:
|
||||
if not considerUserDefinedOp(c, t, body, x, y):
|
||||
if not considerInferDupFromCopy(c, t, body, x, y):
|
||||
fillBody(c, t.elementType, body, x, y)
|
||||
fillBody(c, t.elementType, body, x, y)
|
||||
of tyTuple:
|
||||
fillBodyTup(c, t, body, x, y)
|
||||
of tyVarargs, tyOpenArray:
|
||||
@@ -1168,7 +1112,7 @@ proc symDupPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttache
|
||||
|
||||
result.typ.addParam src
|
||||
|
||||
if g.config.selectedGC in {gcOrc, gcYrc} and
|
||||
if g.config.selectedGC == gcOrc and
|
||||
cyclicType(g, typ.skipTypes(abstractInst)):
|
||||
let cycleParam = newSym(skParam, getIdent(g.cache, "cyclic"),
|
||||
idgen, result, info)
|
||||
@@ -1195,7 +1139,7 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
|
||||
let src = newSym(skParam, getIdent(g.cache, if kind == attachedTrace: "env" else: "src"),
|
||||
idgen, result, info)
|
||||
|
||||
if kind == attachedDestructor and g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc} and
|
||||
if kind == attachedDestructor and g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
|
||||
((g.config.isDefined("nimPreviewNonVarDestructor") and not isDiscriminant) or (typ.kind in {tyRef, tyString, tySequence})):
|
||||
dest.typ = typ
|
||||
else:
|
||||
@@ -1211,7 +1155,7 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
|
||||
if kind notin {attachedDestructor, attachedWasMoved}:
|
||||
result.typ.addParam src
|
||||
|
||||
if kind == attachedAsgn and g.config.selectedGC in {gcOrc, gcYrc} and
|
||||
if kind == attachedAsgn and g.config.selectedGC == gcOrc and
|
||||
cyclicType(g, typ.skipTypes(abstractInst)):
|
||||
let cycleParam = newSym(skParam, getIdent(g.cache, "cyclic"),
|
||||
idgen, result, info)
|
||||
@@ -1240,17 +1184,7 @@ proc genTypeFieldCopy(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
|
||||
info: TLineInfo; idgen: IdGenerator): PSym =
|
||||
if typ.kind == tyDistinct:
|
||||
# For =dup, if the distinct type has a user-defined =copy, don't delegate
|
||||
# to the base type. Instead fall through to the normal produceSym logic
|
||||
# so that fillBody -> considerInferDupFromCopy can synthesize =dup from =copy.
|
||||
if kind == attachedDup:
|
||||
let copyOp = getAttachedOp(g, typ, attachedAsgn)
|
||||
if copyOp != nil and sfOverridden in copyOp.flags:
|
||||
discard "fall through to normal produceSym logic"
|
||||
else:
|
||||
return produceSymDistinctType(g, c, typ, kind, info, idgen)
|
||||
else:
|
||||
return produceSymDistinctType(g, c, typ, kind, info, idgen)
|
||||
return produceSymDistinctType(g, c, typ, kind, info, idgen)
|
||||
|
||||
result = getAttachedOp(g, typ, kind)
|
||||
if result == nil:
|
||||
@@ -1279,22 +1213,14 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
|
||||
else:
|
||||
var tk: TTypeKind
|
||||
var skipped: PType = nil
|
||||
if g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcHooks, gcAtomicArc}:
|
||||
if g.config.selectedGC in {gcArc, gcOrc, gcHooks, gcAtomicArc}:
|
||||
skipped = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink})
|
||||
tk = skipped.kind
|
||||
else:
|
||||
tk = tyNone # no special casing for strings and seqs
|
||||
case tk
|
||||
of tySequence:
|
||||
let needsYrcLock = g.config.selectedGC == gcYrc and
|
||||
kind in {attachedDestructor, attachedSink, attachedAsgn, attachedDeepCopy, attachedDup} and
|
||||
types.canFormAcycle(g, skipped.elementType)
|
||||
# YRC: topology-changing seq ops must hold the mutator (read) lock
|
||||
if needsYrcLock:
|
||||
result.ast[bodyPos].add callCodegenProc(g, "acquireMutatorLock", info)
|
||||
fillSeqOp(a, typ, result.ast[bodyPos], d, src)
|
||||
if needsYrcLock:
|
||||
result.ast[bodyPos].add callCodegenProc(g, "releaseMutatorLock", info)
|
||||
of tyString:
|
||||
fillStrOp(a, typ, result.ast[bodyPos], d, src)
|
||||
else:
|
||||
@@ -1409,7 +1335,7 @@ proc createTypeBoundOps(g: ModuleGraph; c: PContext; orig: PType; info: TLineInf
|
||||
|
||||
# we do not generate '=trace' procs if we
|
||||
# have the cycle detection disabled, saves code size.
|
||||
let lastAttached = if g.config.selectedGC in {gcOrc, gcYrc}: attachedTrace
|
||||
let lastAttached = if g.config.selectedGC == gcOrc: attachedTrace
|
||||
else: attachedSink
|
||||
|
||||
# bug #15122: We need to produce all prototypes before entering the
|
||||
|
||||
@@ -99,7 +99,6 @@ type
|
||||
warnUser = "User",
|
||||
warnGlobalVarConstructorTemporary = "GlobalVarConstructorTemporary",
|
||||
warnImplicitRangeConversion = "ImplicitRangeConversion",
|
||||
warnSystemRangeConversion = "SystemRangeConversion",
|
||||
# hints
|
||||
hintSuccess = "Success", hintSuccessX = "SuccessX",
|
||||
hintCC = "CC",
|
||||
@@ -209,7 +208,6 @@ const
|
||||
warnUser: "$1",
|
||||
warnGlobalVarConstructorTemporary: "global variable '$1' initialization requires a temporary variable",
|
||||
warnImplicitRangeConversion: "implicit range conversion $1",
|
||||
warnSystemRangeConversion: "implicit range conversion $1",
|
||||
hintSuccess: "operation successful: $#",
|
||||
# keep in sync with `testament.isSuccess`
|
||||
hintSuccessX: "$build\n$loc lines; ${sec}s; $mem; proj: $project; out: $output",
|
||||
@@ -264,7 +262,7 @@ type
|
||||
|
||||
proc computeNotesVerbosity(): array[0..3, TNoteKinds] =
|
||||
result = default(array[0..3, TNoteKinds])
|
||||
result[3] = {low(TNoteKind)..high(TNoteKind)} - {warnObservableStores, warnResultUsed, warnAnyEnumConv, warnBareExcept, warnStdPrefix, warnSystemRangeConversion}
|
||||
result[3] = {low(TNoteKind)..high(TNoteKind)} - {warnObservableStores, warnResultUsed, warnAnyEnumConv, warnBareExcept, warnStdPrefix, warnImplicitRangeConversion}
|
||||
result[2] = result[3] - {hintStackTrace, hintExtendedContext, hintDeclaredLoc, hintProcessingStmt}
|
||||
result[1] = result[2] - {warnProveField, warnProveIndex,
|
||||
warnGcUnsafe, hintPath, hintDependency, hintCodeBegin, hintCodeEnd,
|
||||
|
||||
@@ -240,7 +240,7 @@ proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: AbsoluteFile)
|
||||
|
||||
proc setHash*(conf: ConfigRef; fileIdx: FileIndex; hash: string) =
|
||||
assert fileIdx.int32 >= 0
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
conf.m.fileInfos[fileIdx.int32].hash = hash
|
||||
else:
|
||||
shallowCopy(conf.m.fileInfos[fileIdx.int32].hash, hash)
|
||||
@@ -248,7 +248,7 @@ proc setHash*(conf: ConfigRef; fileIdx: FileIndex; hash: string) =
|
||||
|
||||
proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string =
|
||||
assert fileIdx.int32 >= 0
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
result = conf.m.fileInfos[fileIdx.int32].hash
|
||||
else:
|
||||
shallowCopy(result, conf.m.fileInfos[fileIdx.int32].hash)
|
||||
|
||||
@@ -983,7 +983,7 @@ proc genericParamToNif(n: PNode; parent: PNode; c: var TranslationContext) =
|
||||
toNif n, parent, c
|
||||
|
||||
proc addExternName(sym: PSym; c: var TranslationContext) =
|
||||
if sym.loc.snippet != "":
|
||||
if sym.loc.snippet != nil:
|
||||
c.b.addStrLit sym.loc.snippet
|
||||
else:
|
||||
c.b.addStrLit sym.name.s
|
||||
|
||||
@@ -65,7 +65,3 @@ define:useStdoutAsStdmsg
|
||||
@if nimHasVtables:
|
||||
experimental:vtables
|
||||
@end
|
||||
|
||||
@if nimHasImplicitRangeConversion:
|
||||
warning[ImplicitRangeConversion]:off
|
||||
@end
|
||||
|
||||
@@ -195,7 +195,6 @@ type
|
||||
gcRegions = "regions"
|
||||
gcArc = "arc"
|
||||
gcOrc = "orc"
|
||||
gcYrc = "yrc" # thread-safe ORC (concurrent cycle collector)
|
||||
gcAtomicArc = "atomicArc"
|
||||
gcMarkAndSweep = "markAndSweep"
|
||||
gcHooks = "hooks"
|
||||
|
||||
@@ -567,7 +567,7 @@ proc processCompile(c: PContext, n: PNode) =
|
||||
n[i] = c.semConstExpr(c, n[i])
|
||||
case n[i].kind
|
||||
of nkStrLit, nkRStrLit, nkTripleStrLit:
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
result = n[i].strVal
|
||||
else:
|
||||
shallowCopy(result, n[i].strVal)
|
||||
|
||||
@@ -231,7 +231,7 @@ proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile;
|
||||
if optOwnedRefs in oldGlobalOptions:
|
||||
conf.globalOptions.incl {optTinyRtti, optOwnedRefs, optSeqDestructors}
|
||||
defineSymbol(conf.symbols, "nimv2")
|
||||
if conf.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
if conf.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
|
||||
conf.globalOptions.incl {optTinyRtti, optSeqDestructors}
|
||||
defineSymbol(conf.symbols, "nimv2")
|
||||
defineSymbol(conf.symbols, "gcdestructors")
|
||||
@@ -241,8 +241,6 @@ proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile;
|
||||
defineSymbol(conf.symbols, "gcarc")
|
||||
of gcOrc:
|
||||
defineSymbol(conf.symbols, "gcorc")
|
||||
of gcYrc:
|
||||
defineSymbol(conf.symbols, "gcyrc")
|
||||
of gcAtomicArc:
|
||||
defineSymbol(conf.symbols, "gcatomicarc")
|
||||
else:
|
||||
|
||||
@@ -855,7 +855,7 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
|
||||
appendToModule(c.module, result)
|
||||
trackStmt(c, c.module, result, isTopLevel = true)
|
||||
if optMultiMethods notin c.config.globalOptions and
|
||||
c.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc} and
|
||||
c.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
|
||||
Feature.vtables in c.config.features:
|
||||
sortVTableDispatchers(c.graph)
|
||||
|
||||
|
||||
@@ -981,7 +981,7 @@ proc setGenericParams(c: PContext, n, expectedParams: PNode) =
|
||||
if e.typ == nil:
|
||||
n[i].typ = errorType(c)
|
||||
else:
|
||||
n[i].typ = e.typ
|
||||
n[i].typ = e.typ.skipTypes({tyTypeDesc})
|
||||
|
||||
proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym, doError: bool): PNode =
|
||||
assert n.kind == nkBracketExpr
|
||||
|
||||
@@ -333,7 +333,7 @@ proc isCastable(c: PContext; dst, src: PType, info: TLineInfo): bool =
|
||||
if skipTypes(dst, abstractInst).kind == tyBuiltInTypeClass:
|
||||
return false
|
||||
let conf = c.config
|
||||
if conf.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc}:
|
||||
if conf.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
|
||||
let d = skipTypes(dst, abstractInst)
|
||||
let s = skipTypes(src, abstractInst)
|
||||
if d.kind == tyRef and s.kind == tyRef and s[0].isFinal != d[0].isFinal:
|
||||
@@ -813,7 +813,7 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PTyp
|
||||
inc(lastIndex)
|
||||
if isGeneric:
|
||||
for i in 0..<result.len:
|
||||
if result[i].typ != nil and isIntLit(result[i].typ):
|
||||
if isIntLit(result[i].typ):
|
||||
# generic instantiation strips int lit type which makes conversions fail
|
||||
result[i].typ = nil
|
||||
result.typ = nil # current result.typ is invalid, index type is nil
|
||||
@@ -2800,7 +2800,7 @@ proc semSetConstr(c: PContext, n: PNode, expectedType: PType = nil): PNode =
|
||||
expectedElementType = typ
|
||||
if isGeneric:
|
||||
for i in 0..<n.len:
|
||||
if n[i].typ != nil and isIntLit(n[i].typ):
|
||||
if isIntLit(n[i].typ):
|
||||
# generic instantiation strips int lit type which makes conversions fail
|
||||
n[i].typ = nil
|
||||
result.add n[i]
|
||||
@@ -2913,7 +2913,7 @@ proc semTupleFieldsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType
|
||||
result.add n[i]
|
||||
if isGeneric:
|
||||
for i in 0..<result.len:
|
||||
if result[i][1].typ != nil and isIntLit(result[i][1].typ):
|
||||
if isIntLit(result[i][1].typ):
|
||||
# generic instantiation strips int lit type which makes conversions fail
|
||||
result[i][1].typ = nil
|
||||
result.typ = makeTypeFromExpr(c, result.copyTree)
|
||||
@@ -2954,7 +2954,7 @@ proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedT
|
||||
addSonSkipIntLit(typ, n[i].typ.skipTypes({tySink}), c.idgen)
|
||||
if isGeneric:
|
||||
for i in 0..<result.len:
|
||||
if result[i].typ != nil and isIntLit(result[i].typ):
|
||||
if isIntLit(result[i].typ):
|
||||
# generic instantiation strips int lit type which makes conversions fail
|
||||
result[i].typ = nil
|
||||
result.typ = makeTypeFromExpr(c, result.copyTree)
|
||||
|
||||
@@ -236,8 +236,6 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym)
|
||||
let complexObj = containsGarbageCollectedRef(t) or
|
||||
hasDestructor(t)
|
||||
result = newIntNodeT(toInt128(ord(not complexObj)), traitCall, c.idgen, c.graph)
|
||||
of "canFormCycles":
|
||||
result = newIntNodeT(toInt128(ord(types.canFormAcycle(c.graph, operand))), traitCall, c.idgen, c.graph)
|
||||
of "hasDefaultValue":
|
||||
result = newIntNodeT(toInt128(ord(not operand.requiresInit)), traitCall, c.idgen, c.graph)
|
||||
of "isNamedTuple":
|
||||
|
||||
@@ -168,7 +168,7 @@ proc isRangeSupertype(conf: ConfigRef; wider, narrower: PType): bool =
|
||||
# int -> float ranges; warn
|
||||
result = false
|
||||
|
||||
proc shouldWarnRangeConversion(conf: ConfigRef; info: TLineInfo; formalType, argType: PType): bool =
|
||||
proc shouldWarnRangeConversion(conf: ConfigRef; formalType, argType: PType): bool =
|
||||
## Determine if an implicit range conversion should warn
|
||||
## We warn on conversions that are likely to cause panics
|
||||
let f = formalType.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct})
|
||||
@@ -176,19 +176,7 @@ proc shouldWarnRangeConversion(conf: ConfigRef; info: TLineInfo; formalType, arg
|
||||
if f.kind == tyRange:
|
||||
# Only warn if formal range doesn't fully contain argument range
|
||||
# Check if the ranges don't perfectly overlap
|
||||
if a.kind == tyInt and f.sym != nil and f.sym.owner != nil and
|
||||
sfSystemModule in f.sym.owner.flags and
|
||||
(f.sym.name.s == "Positive" or
|
||||
f.sym.name.s == "Natural"):
|
||||
# Positive and Natural are special cases that we do not warn on with
|
||||
# ImplicitRangeConversion, but may warn on with systemRangeConversion
|
||||
# if that warning is enabled.
|
||||
if conf.hasWarn(warnSystemRangeConversion):
|
||||
message(conf, info, warnSystemRangeConversion,
|
||||
typeToString(argType) & " -> " & typeToString(formalType))
|
||||
result = false
|
||||
else:
|
||||
result = not isRangeSupertype(conf, f, a)
|
||||
result = not isRangeSupertype(conf, f, a)
|
||||
else:
|
||||
result = false
|
||||
|
||||
@@ -1550,8 +1538,7 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
|
||||
# Check for implicit range conversions
|
||||
if n.kind == nkHiddenStdConv and (not tracked.isArrayIndexing) and
|
||||
n[1].kind notin {nkCharLit..nkUInt64Lit, nkFloatLit..nkFloat128Lit} and
|
||||
shouldWarnRangeConversion(tracked.config, n.info, n.typ, n[1].typ):
|
||||
shouldWarnRangeConversion(tracked.config, n.typ, n[1].typ):
|
||||
message(tracked.config, n.info, warnImplicitRangeConversion,
|
||||
typeToString(n[1].typ) & " -> " & typeToString(n.typ))
|
||||
|
||||
@@ -1769,7 +1756,7 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
|
||||
let param = params[i].sym
|
||||
let typ = param.typ
|
||||
if isSinkTypeForParam(typ) or
|
||||
(t.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc} and
|
||||
(t.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
|
||||
(isClosure(typ.skipTypes(abstractInst)) or param.id in t.escapingParams)):
|
||||
createTypeBoundOps(t, typ, param.info)
|
||||
if isOutParam(typ) and param.id notin t.init and s.magic == mNone:
|
||||
|
||||
@@ -2175,7 +2175,7 @@ proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) =
|
||||
template notRefc: bool =
|
||||
# fixes refc with non-var destructor; cancel warnings (#23156)
|
||||
c.config.backend == backendJs or
|
||||
c.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}
|
||||
c.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}
|
||||
let cond = case op
|
||||
of attachedWasMoved:
|
||||
t.len == 2 and t.returnType == nil and t.firstParamType.kind == tyVar
|
||||
|
||||
@@ -1014,7 +1014,7 @@ proc skipGenericInvocation(t: PType): PType {.inline.} =
|
||||
proc tryAddInheritedFields(c: PContext, check: var IntSet, pos: var int,
|
||||
obj: PType, n: PNode, isPartial = false, innerObj: PType = nil): bool =
|
||||
if ((not isPartial) and (obj.kind notin {tyObject, tyGenericParam} or tfFinal in obj.flags)) or
|
||||
(innerObj != nil and obj.id == innerObj.id):
|
||||
(innerObj != nil and obj.sym.id == innerObj.sym.id):
|
||||
localError(c.config, n.info, "Cannot inherit from: '" & $obj & "'")
|
||||
result = false
|
||||
elif obj.kind == tyObject:
|
||||
@@ -1149,7 +1149,7 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType =
|
||||
result = t
|
||||
else: discard
|
||||
if result.kind == tyRef and
|
||||
c.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc} and
|
||||
c.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
|
||||
tfTriggersCompileTime notin result.flags:
|
||||
result.incl tfHasAsgn
|
||||
|
||||
@@ -1203,15 +1203,7 @@ proc addImplicitGeneric(c: PContext; typeClass: PType, typId: PIdent;
|
||||
# is this a bindOnce type class already present in the param list?
|
||||
for i in 0..<genericParams.len:
|
||||
if genericParams[i].sym.name.id == finalTypId.id:
|
||||
if typeClass.kind == tyStatic and genericParams[i].typ.kind != tyStatic:
|
||||
# The base type (e.g. from `auto`) was already added as a generic param,
|
||||
# but `static[auto]` requires upgrading it to a `tyStatic` wrapper so
|
||||
# it is instantiated as a compile-time value (`skConst`).
|
||||
genericParams[i].sym.linkTo(typeClass)
|
||||
typeClass.incl tfImplicitTypeParam
|
||||
return typeClass
|
||||
else:
|
||||
return genericParams[i].typ
|
||||
return genericParams[i].typ
|
||||
|
||||
let owner = if typeClass.sym != nil: typeClass.sym
|
||||
else: getCurrOwner(c)
|
||||
@@ -1747,7 +1739,6 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
|
||||
var isConcrete = true
|
||||
let rType = m.call[0].typ
|
||||
let mIndex = if rType != nil: rType.len - 1 else: -1
|
||||
var hasForwardTypeParam = false
|
||||
for i in 1..<m.call.len:
|
||||
var typ = m.call[i].typ
|
||||
# is this a 'typedesc' *parameter*? If so, use the typedesc type,
|
||||
@@ -1764,36 +1755,13 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
|
||||
skip = false
|
||||
addToResult(typ, skip)
|
||||
|
||||
if typ.kind == tyForward:
|
||||
hasForwardTypeParam = true
|
||||
|
||||
if isConcrete:
|
||||
if s.ast == nil and s.typ.kind != tyCompositeTypeClass:
|
||||
# XXX: What kind of error is this? is it still relevant?
|
||||
localError(c.config, n.info, errCannotInstantiateX % s.name.s)
|
||||
result = newOrPrevType(tyError, prev, c)
|
||||
elif containsGenericInvocationWithForward(n[0]) or hasForwardTypeParam:
|
||||
# isConcrete == false means this generic type is not instanciated here because it invoked with generic parameters.
|
||||
# Even if isConcrete == true, don't instanciate it now if there are any `tyForward` type params.
|
||||
# Such `tyForward` type params will be semchecked later and we can instanciate this next time.
|
||||
# Some generic types like std/options.Option[T] needs a type kinds of the given type argument.
|
||||
|
||||
# return `tyForward` instead of `tyGenericInvocation` because:
|
||||
# ```nim
|
||||
# type Foo = object
|
||||
# x: Option[Foo]
|
||||
# ```
|
||||
# returning `tyGenericInvocation` makes `Option[Foo]` to `tyGenericInvocation` and
|
||||
# next time `semGeneric` is called with `Option[Foo]`, containsGenericType(typeof(`Foo`)) == true
|
||||
# and `isConcrete == false`.
|
||||
if prev == nil:
|
||||
result = newTypeS(tyForward, c)
|
||||
result.sym = s
|
||||
else:
|
||||
assignType(result, newTypeS(tyForward, c))
|
||||
result.sym = s
|
||||
elif containsGenericInvocationWithForward(n[0]):
|
||||
c.forwardTypeUpdates.add (result, n) #fixes 1500
|
||||
return
|
||||
else:
|
||||
result = instGenericContainer(c, n.info, result,
|
||||
allowMetaTypes = false)
|
||||
@@ -2081,9 +2049,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym =
|
||||
# proc signature for example
|
||||
if c.inGenericInst > 0:
|
||||
let bound = result.typ.elementType.sym
|
||||
# the symbol may still point to the uninstantiated generic body type
|
||||
if bound != nil and bound.typ == result.typ.elementType:
|
||||
return bound
|
||||
if bound != nil: return bound
|
||||
return result
|
||||
if result.typ.sym == nil:
|
||||
localError(c.config, n.info, errTypeExpected)
|
||||
@@ -2424,7 +2390,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
|
||||
if n.kind == nkIteratorTy and result.kind == tyProc:
|
||||
result.incl(tfIterator)
|
||||
if result.callConv == ccClosure and c.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc}:
|
||||
if result.callConv == ccClosure and c.config.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
|
||||
result.incl tfHasAsgn
|
||||
of nkEnumTy: result = semEnum(c, n, prev)
|
||||
of nkType: result = n.typ
|
||||
|
||||
@@ -563,26 +563,6 @@ proc eraseVoidParams*(t: PType) =
|
||||
setLen t.n.sons, pos
|
||||
break
|
||||
|
||||
proc eraseTupleVoidFields*(t: PType) =
|
||||
## Remove void fields from a named tuple type, compacting both `t.n`
|
||||
## (the field symbol nodes) and `t.sonsImpl` (the child types).
|
||||
if t.n == nil: return # anonymous tuple, nothing to compact
|
||||
for i in 0..<t.kidsLen:
|
||||
if t.n[i].kind == nkRecList or t[i].kind == tyVoid:
|
||||
# found first void field, compact from here
|
||||
var pos = i
|
||||
for j in i+1..<t.kidsLen:
|
||||
if t[j].kind != tyVoid and j < t.n.len and t.n[j].kind != nkRecList:
|
||||
t.n[pos] = t.n[j]
|
||||
t[pos] = t[j]
|
||||
if t.n[pos].kind == nkSym:
|
||||
t.n[pos].sym.position = pos
|
||||
inc pos
|
||||
# else: skip void entries
|
||||
setLen t.n.sons, pos
|
||||
t.setSonsLen pos
|
||||
break
|
||||
|
||||
proc skipIntLiteralParams*(t: PType; idgen: IdGenerator) =
|
||||
for i, p in t.ikids:
|
||||
if p == nil: continue
|
||||
@@ -788,8 +768,6 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
|
||||
propagateFieldFlags(result, result.n)
|
||||
if result.kind == tyObject and cl.c.computeRequiresInit(cl.c, result):
|
||||
result.incl tfRequiresInit
|
||||
if result.kind == tyTuple:
|
||||
eraseTupleVoidFields(result)
|
||||
|
||||
of tyProc:
|
||||
eraseVoidParams(result)
|
||||
|
||||
@@ -41,7 +41,6 @@ type
|
||||
CoType
|
||||
CoOwnerSig
|
||||
CoIgnoreRange
|
||||
CoIgnoreRangeInArray
|
||||
CoConsiderOwned
|
||||
CoDistinct
|
||||
CoHashTypeInsideNode
|
||||
@@ -221,17 +220,10 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
|
||||
else:
|
||||
for a in t.kids: c.hashType a, flags+{CoIgnoreRange}, conf
|
||||
of tyRange:
|
||||
if {CoIgnoreRange, CoIgnoreRangeInArray} * flags == {}:
|
||||
if CoIgnoreRange notin flags:
|
||||
c &= char(t.kind)
|
||||
c.hashTree(t.n, {}, conf)
|
||||
c.hashType(t.elementType, flags, conf)
|
||||
elif CoIgnoreRangeInArray in flags:
|
||||
# include only the length of the range (not its specific bounds)
|
||||
c &= char(t.kind)
|
||||
let l = lengthOrd(conf, t)
|
||||
lowlevel l
|
||||
else:
|
||||
c.hashType(t.elementType, flags, conf)
|
||||
c.hashType(t.elementType, flags, conf)
|
||||
of tyStatic:
|
||||
c &= char(t.kind)
|
||||
c.hashTree(t.n, {}, conf)
|
||||
@@ -261,7 +253,7 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
|
||||
if tfVarargs in t.flags: c &= ".varargs"
|
||||
of tyArray:
|
||||
c &= char(t.kind)
|
||||
c.hashType(t.indexType, flags-{CoIgnoreRange}+{CoIgnoreRangeInArray}, conf)
|
||||
c.hashType(t.indexType, flags-{CoIgnoreRange}, conf)
|
||||
c.hashType(t.elementType, flags-{CoIgnoreRange}, conf)
|
||||
else:
|
||||
c &= char(t.kind)
|
||||
|
||||
@@ -160,7 +160,8 @@ proc matchGenericParam(m: var TCandidate, formal: PType, n: PNode) =
|
||||
arg = newTypeS(tyStatic, m.c, son = evaluated.typ)
|
||||
arg.n = evaluated
|
||||
elif formalBase.kind == tyTypeDesc:
|
||||
discard # if arg is not tyTypeDesc, typeRel will report the mismatch
|
||||
if arg.kind != tyTypeDesc:
|
||||
arg = makeTypeDesc(m.c, arg)
|
||||
else:
|
||||
arg = arg.skipTypes({tyTypeDesc})
|
||||
let tm = typeRel(m, formal, arg)
|
||||
@@ -1677,6 +1678,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
|
||||
elif a.kind == tyGenericInst:
|
||||
if roota.base == rootf.base:
|
||||
let nextFlags = flags + {trNoCovariance}
|
||||
var hasCovariance = false
|
||||
# YYYY
|
||||
result = isEqual
|
||||
|
||||
@@ -1688,7 +1690,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
|
||||
if res notin {isEqual, isGeneric}:
|
||||
if trNoCovariance notin flags and ff.kind == aa.kind:
|
||||
let paramFlags = rootf.base[i-1].flags
|
||||
let hasCovariance =
|
||||
hasCovariance =
|
||||
if tfCovariant in paramFlags:
|
||||
if tfWeakCovariant in paramFlags:
|
||||
isCovariantPtr(c, ff, aa)
|
||||
@@ -1699,36 +1701,35 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
|
||||
typeRel(c, aa, ff, flags) == isSubtype
|
||||
if hasCovariance:
|
||||
continue
|
||||
result = isNone
|
||||
break
|
||||
|
||||
if result != isNone:
|
||||
if prev == nil: put(c, f, a)
|
||||
return isNone
|
||||
if prev == nil: put(c, f, a)
|
||||
else:
|
||||
let fKind = rootf.last.kind
|
||||
if fKind in {tyAnd, tyOr}:
|
||||
result = typeRel(c, last(f), a, flags)
|
||||
if result != isNone: put(c, f, a)
|
||||
return
|
||||
|
||||
let fKind = rootf.last.kind
|
||||
if fKind in {tyAnd, tyOr}:
|
||||
result = typeRel(c, last(f), a, flags)
|
||||
if result != isNone: put(c, f, a)
|
||||
return
|
||||
var aAsObject = roota.last
|
||||
|
||||
var aAsObject = roota.last
|
||||
if fKind in {tyRef, tyPtr}:
|
||||
if aAsObject.kind == tyObject:
|
||||
# bug #7600, tyObject cannot be passed
|
||||
# as argument to tyRef/tyPtr
|
||||
return isNone
|
||||
elif aAsObject.kind == fKind:
|
||||
aAsObject = aAsObject.base
|
||||
|
||||
if fKind in {tyRef, tyPtr}:
|
||||
if aAsObject.kind == tyObject:
|
||||
# bug #7600, tyObject cannot be passed
|
||||
# as argument to tyRef/tyPtr
|
||||
return isNone
|
||||
elif aAsObject.kind == fKind:
|
||||
aAsObject = aAsObject.base
|
||||
if aAsObject.kind == tyObject and trIsOutParam notin flags:
|
||||
let baseType = aAsObject.base
|
||||
if baseType != nil:
|
||||
if tfFinal notin aAsObject.flags:
|
||||
inc c.inheritancePenalty, 1 + int(c.inheritancePenalty < 0)
|
||||
let ret = typeRel(c, f, baseType, flags)
|
||||
return if ret in {isEqual,isGeneric}: isSubtype else: ret
|
||||
|
||||
if aAsObject.kind == tyObject and trIsOutParam notin flags:
|
||||
let baseType = aAsObject.base
|
||||
if baseType != nil:
|
||||
if tfFinal notin aAsObject.flags:
|
||||
inc c.inheritancePenalty, 1 + int(c.inheritancePenalty < 0)
|
||||
let ret = typeRel(c, f, baseType, flags)
|
||||
return if ret in {isEqual,isGeneric}: isSubtype else: ret
|
||||
result = isNone
|
||||
else:
|
||||
assert last(origF) != nil
|
||||
result = typeRel(c, last(origF), a, flags)
|
||||
@@ -2185,9 +2186,9 @@ proc implicitConv(kind: TNodeKind, f: PType, arg: PNode, m: TCandidate,
|
||||
result.typ = errorType(c)
|
||||
else:
|
||||
result.typ = f.skipTypes({tySink})
|
||||
# keep varness, but don't wrap lent types with var
|
||||
# keep varness
|
||||
if arg.typ != nil and arg.typ.kind == tyVar:
|
||||
result.typ = toVar(result.typ.skipTypes({tyLent}), tyVar, c.idgen)
|
||||
result.typ = toVar(result.typ, tyVar, c.idgen)
|
||||
# copy the tfVarIsPtr flag
|
||||
result.typ.flags = arg.typ.flags
|
||||
else:
|
||||
|
||||
@@ -37,7 +37,7 @@ proc spawnResult*(t: PType; inParallel: bool): TSpawnResult =
|
||||
else: srFlowVar
|
||||
|
||||
proc flowVarKind(c: ConfigRef, t: PType): TFlowVarKind =
|
||||
if c.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc}: fvBlob
|
||||
if c.selectedGC in {gcArc, gcOrc, gcAtomicArc}: fvBlob
|
||||
elif t.skipTypes(abstractInst).kind in {tyRef, tyString, tySequence}: fvGC
|
||||
elif containsGarbageCollectedRef(t): fvInvalid
|
||||
else: fvBlob
|
||||
@@ -66,7 +66,7 @@ proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; idgen: IdGenerator;
|
||||
vpart[2] = if varInit.isNil: v else: vpart[1]
|
||||
varSection.add vpart
|
||||
if varInit != nil:
|
||||
if g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc}:
|
||||
if g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc}:
|
||||
# inject destructors pass will do its own analysis
|
||||
varInit.add newFastMoveStmt(g, newSymNode(result), v)
|
||||
else:
|
||||
|
||||
@@ -120,7 +120,7 @@ template decodeBx(k: untyped) {.dirty.} =
|
||||
ensureKind(k)
|
||||
|
||||
template move(a, b: untyped) {.dirty.} =
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
a = move b
|
||||
else:
|
||||
system.shallowCopy(a, b)
|
||||
@@ -557,7 +557,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||
# Used to keep track of where the execution is resumed.
|
||||
var savedPC = -1
|
||||
var savedFrame: PStackFrame = nil
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
template updateRegsAlias = discard
|
||||
template regs: untyped = tos.slots
|
||||
else:
|
||||
|
||||
@@ -1144,8 +1144,6 @@ semantic analysis). Assignments from the base type to one of its subrange types
|
||||
A subrange type has the same size as its base type (`int` in the
|
||||
Subrange example).
|
||||
|
||||
Implicit "downsizing" conversions to range types (for example, `int -> range[0..255]` or `range[1..256] -> range[0..255]`) emit the `ImplicitRangeConversion` warning. Conversions that are clearly safe (for example, `range[0..255] -> range[0..65535]`) and any explicit casts do not trigger this warning. Conversions from `int` to common subranges such as `Natural` or `Positive` do not trigger this warning by default, but can be enabled with `--warning:systemRangeConversion`.
|
||||
|
||||
|
||||
Pre-defined floating-point types
|
||||
--------------------------------
|
||||
@@ -7906,7 +7904,7 @@ alignment requirement of the type are ignored.
|
||||
main()
|
||||
```
|
||||
|
||||
This pragma has no effect on the JavaScript backend and may significantly increase memory usage with the `--mm:refc` option.
|
||||
This pragma has no effect on the JS backend.
|
||||
|
||||
|
||||
Noalias pragma
|
||||
|
||||
@@ -2127,7 +2127,7 @@ can be used in an `isolate` context:
|
||||
`=destroy`(dest.value)
|
||||
```
|
||||
|
||||
The `.sendable` pragma itself is an experimental, unchecked, unsafe annotation. It is
|
||||
The `.sendable` pragma itself is an experimenal, unchecked, unsafe annotation. It is
|
||||
currently only used by `Isolated[T]`.
|
||||
|
||||
Virtual pragma
|
||||
|
||||
@@ -276,9 +276,9 @@ This parser has 2 modes for inline markup:
|
||||
|
||||
2) Compatibility mode which is RST rules.
|
||||
|
||||
.. Note:: in both modes the parser interprets text between single
|
||||
.. Note:: in both modes the parser interpretes text between single
|
||||
backticks (code) identically:
|
||||
backslash does not escape; the only exception: ``\`` followed by `
|
||||
backslash does not escape; the only exception: ``\`` folowed by `
|
||||
does escape so that we can always input a single backtick ` in
|
||||
inline code. However that makes impossible to input code with
|
||||
``\`` at the end in *single* backticks, one must use *double*
|
||||
|
||||
@@ -52,7 +52,7 @@ Options:
|
||||
nimgrep --filenames # In current dir
|
||||
nimgrep --filenames "" DIRECTORY
|
||||
# Note empty pattern "", lists all files in DIRECTORY
|
||||
* Interpret patterns:
|
||||
* Interprete patterns:
|
||||
--peg PATTERN and PAT are Peg
|
||||
--re PATTERN and PAT are regular expressions (default)
|
||||
--rex, -x use the "extended" syntax for the regular expression
|
||||
|
||||
@@ -27,7 +27,7 @@ Nim runs on a wide variety of platforms. Support on amd64 and i386 is tested reg
|
||||
- ppc64el (aka ppc64le)
|
||||
- riscv64
|
||||
|
||||
The following platforms are rarely tested:
|
||||
The following platforms are seldomly tested:
|
||||
|
||||
- alpha
|
||||
- hppa
|
||||
|
||||
@@ -20,8 +20,8 @@ notation meaning
|
||||
as they succeed. Indicate success if all succeeded.
|
||||
Otherwise, do not consume any text and indicate failure.
|
||||
The sequence's precedence is higher than that of ordered
|
||||
choice: ``A B / C`` means ``(A B) / C`` and
|
||||
not ``A (B / C)``.
|
||||
choice: ``A B / C`` means ``(A B) / Z`` and
|
||||
not ``A (B / Z)``.
|
||||
``(E)`` Grouping: Parenthesis can be used to change
|
||||
operator priority.
|
||||
``{E}`` Capture: Apply expression `E` and store the substring
|
||||
|
||||
@@ -1159,8 +1159,8 @@ In Nim new types can be defined within a `type` statement:
|
||||
|
||||
```nim test = "nim c $1"
|
||||
type
|
||||
BiggestInt = int64 # biggest integer type that is available
|
||||
BiggestFloat = float64 # biggest float type that is available
|
||||
biggestInt = int64 # biggest integer type that is available
|
||||
biggestFloat = float64 # biggest float type that is available
|
||||
```
|
||||
|
||||
Enumeration and object types may only be defined within a
|
||||
|
||||
4
koch.nim
4
koch.nim
@@ -12,9 +12,9 @@
|
||||
const
|
||||
# examples of possible values for repos: Head, ea82b54
|
||||
NimbleStableCommit = "9207e8b2bbdf66b5a4d1020214cff44d2d30df92" # 0.20.1
|
||||
AtlasStableCommit = "ff1f4289482dce94ba9f95b3b0ae16d16e21eb3d" # 0.10.1
|
||||
AtlasStableCommit = "2aa62121b40d580aa2fb27920a37b938d36c5f57" # 0.9.4
|
||||
ChecksumsStableCommit = "0b8e46379c5bc1bf73d8b3011908389c60fb9b98" # 2.0.1
|
||||
SatStableCommit = "e63eaea8baf00bed8bcd5a29ffd8823abb265b39"
|
||||
SatStableCommit = "faf1617f44d7632ee9601ebc13887644925dcc01"
|
||||
|
||||
NimonyStableCommit = "deb9b50c573fb55e071825ab55385e293b7216d5" # unversioned \
|
||||
# Note that Nimony uses Nim as a git submodule but we don't want to install
|
||||
|
||||
@@ -433,15 +433,15 @@ when defined(nimHasNoReturnError):
|
||||
else:
|
||||
{.pragma: errorNoReturn.}
|
||||
|
||||
proc error*(msg: string, n: NimNode = nil) {.magic: "NError", gcsafe, errorNoReturn.}
|
||||
proc error*(msg: string, n: NimNode = nil) {.magic: "NError", benign, errorNoReturn.}
|
||||
## Writes an error message at compile time. The optional `n: NimNode`
|
||||
## parameter is used as the source for file and line number information in
|
||||
## the compilation error message.
|
||||
|
||||
proc warning*(msg: string, n: NimNode = nil) {.magic: "NWarning", gcsafe.}
|
||||
proc warning*(msg: string, n: NimNode = nil) {.magic: "NWarning", benign.}
|
||||
## Writes a warning message at compile time.
|
||||
|
||||
proc hint*(msg: string, n: NimNode = nil) {.magic: "NHint", gcsafe.}
|
||||
proc hint*(msg: string, n: NimNode = nil) {.magic: "NHint", benign.}
|
||||
## Writes a hint message at compile time.
|
||||
|
||||
proc newStrLitNode*(s: string): NimNode {.noSideEffect.} =
|
||||
@@ -511,7 +511,7 @@ proc genSym*(kind: NimSymKind = nskLet; ident = ""): NimNode {.
|
||||
## Generates a fresh symbol that is guaranteed to be unique. The symbol
|
||||
## needs to occur in a declaration context.
|
||||
|
||||
proc callsite*(): NimNode {.magic: "NCallSite", gcsafe, deprecated:
|
||||
proc callsite*(): NimNode {.magic: "NCallSite", benign, deprecated:
|
||||
"Deprecated since v0.18.1; use `varargs[untyped]` in the macro prototype instead".}
|
||||
## Returns the AST of the invocation expression that invoked this macro.
|
||||
# see https://github.com/nim-lang/RFCs/issues/387 as candidate replacement.
|
||||
@@ -933,7 +933,7 @@ proc eqIdent*(a: NimNode; b: NimNode): bool {.magic: "EqIdent", noSideEffect.}
|
||||
|
||||
const collapseSymChoice = not defined(nimLegacyMacrosCollapseSymChoice)
|
||||
|
||||
proc treeTraverse(n: NimNode; res: var string; level = 0; isLisp = false, indented = false) {.gcsafe.} =
|
||||
proc treeTraverse(n: NimNode; res: var string; level = 0; isLisp = false, indented = false) {.benign.} =
|
||||
if level > 0:
|
||||
if indented:
|
||||
res.add("\n")
|
||||
@@ -982,21 +982,21 @@ proc treeTraverse(n: NimNode; res: var string; level = 0; isLisp = false, indent
|
||||
if isLisp:
|
||||
res.add(")")
|
||||
|
||||
proc treeRepr*(n: NimNode): string {.gcsafe.} =
|
||||
proc treeRepr*(n: NimNode): string {.benign.} =
|
||||
## Convert the AST `n` to a human-readable tree-like string.
|
||||
##
|
||||
## See also `repr`, `lispRepr`_, and `astGenRepr`_.
|
||||
result = ""
|
||||
n.treeTraverse(result, isLisp = false, indented = true)
|
||||
|
||||
proc lispRepr*(n: NimNode; indented = false): string {.gcsafe.} =
|
||||
proc lispRepr*(n: NimNode; indented = false): string {.benign.} =
|
||||
## Convert the AST `n` to a human-readable lisp-like string.
|
||||
##
|
||||
## See also `repr`, `treeRepr`_, and `astGenRepr`_.
|
||||
result = ""
|
||||
n.treeTraverse(result, isLisp = true, indented = indented)
|
||||
|
||||
proc astGenRepr*(n: NimNode): string {.gcsafe.} =
|
||||
proc astGenRepr*(n: NimNode): string {.benign.} =
|
||||
## Convert the AST `n` to the code required to generate that AST.
|
||||
##
|
||||
## See also `repr`_, `treeRepr`_, and `lispRepr`_.
|
||||
@@ -1005,7 +1005,7 @@ proc astGenRepr*(n: NimNode): string {.gcsafe.} =
|
||||
NodeKinds = {nnkEmpty, nnkIdent, nnkSym, nnkNone, nnkCommentStmt}
|
||||
LitKinds = {nnkCharLit..nnkInt64Lit, nnkFloatLit..nnkFloat64Lit, nnkStrLit..nnkTripleStrLit}
|
||||
|
||||
proc traverse(res: var string, level: int, n: NimNode) {.gcsafe.} =
|
||||
proc traverse(res: var string, level: int, n: NimNode) {.benign.} =
|
||||
for i in 0..level-1: res.add " "
|
||||
if n.kind in NodeKinds:
|
||||
res.add("new" & ($n.kind).substr(3) & "Node(")
|
||||
|
||||
@@ -15,9 +15,6 @@ template formatStr*(howExpr, namegetter, idgetter): untyped =
|
||||
val.add(how[i])
|
||||
i += 1
|
||||
else:
|
||||
if i + 1 >= how.len:
|
||||
raise newException(ValueError, "Syntax error in format string at " & $i)
|
||||
|
||||
if how[i + 1] == '$':
|
||||
val.add('$')
|
||||
i += 2
|
||||
@@ -30,7 +27,7 @@ template formatStr*(howExpr, namegetter, idgetter): untyped =
|
||||
i += 1
|
||||
var id {.inject.} = 0
|
||||
while i < how.len and how[i] in {'0'..'9'}:
|
||||
id = (id * 10) + (ord(how[i]) - ord('0'))
|
||||
id += (id * 10) + (ord(how[i]) - ord('0'))
|
||||
i += 1
|
||||
val.add(idgetter)
|
||||
lastNum = id + 1
|
||||
@@ -47,8 +44,6 @@ template formatStr*(howExpr, namegetter, idgetter): untyped =
|
||||
while i < how.len and how[i] != '}':
|
||||
name.add(how[i])
|
||||
i += 1
|
||||
if i >= how.len or how[i] != '}':
|
||||
raise newException(ValueError, "Syntax error in format string at " & $i)
|
||||
i += 1
|
||||
val.add(namegetter)
|
||||
else:
|
||||
|
||||
@@ -1946,14 +1946,9 @@ proc withTimeout*[T](fut: Future[T], timeout: int): owned(Future[bool]) =
|
||||
retFuture.fail(fut.error)
|
||||
else:
|
||||
retFuture.complete(true)
|
||||
# Timeout side lost; drop its callback to avoid retaining closures/futures.
|
||||
timeoutFuture.clearCallbacks()
|
||||
timeoutFuture.callback =
|
||||
proc () =
|
||||
if not retFuture.finished:
|
||||
retFuture.complete(false)
|
||||
# Wrapped future side lost; drop its callback to avoid retaining closures/futures.
|
||||
fut.clearCallbacks()
|
||||
if not retFuture.finished: retFuture.complete(false)
|
||||
return retFuture
|
||||
|
||||
proc accept*(socket: AsyncFD,
|
||||
|
||||
@@ -188,7 +188,7 @@ proc processRequest(
|
||||
# \n
|
||||
request.headers.clear()
|
||||
request.body = ""
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
request.hostname = address
|
||||
else:
|
||||
request.hostname.shallowCopy(address)
|
||||
|
||||
@@ -255,9 +255,6 @@ proc decode*(s: string): string =
|
||||
while inputIndex <= inputEnds:
|
||||
while s[inputIndex] in {'\n', '\r', ' '}:
|
||||
inc inputIndex
|
||||
# double check inputIndex as it can be incremented due to whitespace
|
||||
if inputIndex > inputEnds:
|
||||
break
|
||||
inputChar(a)
|
||||
inputChar(b)
|
||||
inputChar(c)
|
||||
|
||||
@@ -36,7 +36,7 @@ when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
const defaultStackSize = 512 * 1024
|
||||
const useOrcArc = defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc)
|
||||
const useOrcArc = defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc)
|
||||
|
||||
when useOrcArc:
|
||||
proc nimGC_setStackBottom*(theStackBottom: pointer) = discard
|
||||
|
||||
@@ -866,7 +866,7 @@ proc parseJson(p: var JsonParser; rawIntegers, rawFloats: bool, depth = 0): Json
|
||||
case p.tok
|
||||
of tkString:
|
||||
# we capture 'p.a' here, so we need to give it a fresh buffer afterwards:
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
result = JsonNode(kind: JString, str: move p.a)
|
||||
else:
|
||||
result = JsonNode(kind: JString)
|
||||
|
||||
@@ -305,7 +305,7 @@ proc store*[T](s: Stream, data: sink T) =
|
||||
|
||||
var stored = initIntSet()
|
||||
var d: T
|
||||
when defined(gcArc) or defined(gcOrc)or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc)or defined(gcAtomicArc):
|
||||
d = data
|
||||
else:
|
||||
shallowCopy(d, data)
|
||||
@@ -334,7 +334,7 @@ proc `$$`*[T](x: sink T): string =
|
||||
else:
|
||||
var stored = initIntSet()
|
||||
var d: T
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
d = x
|
||||
else:
|
||||
shallowCopy(d, x)
|
||||
|
||||
@@ -14,306 +14,149 @@
|
||||
## Supported Syntax
|
||||
## ================
|
||||
##
|
||||
## The syntax described here applies to the default way the parser works.
|
||||
## The behavior is configurable, though, and two additional modes
|
||||
## are supported, see the details: `Parser Modes`_.
|
||||
## The following syntax is supported when arguments for the `shortNoVal` and
|
||||
## `longNoVal` parameters, which are
|
||||
## `described later<#nimshortnoval-and-nimlongnoval>`_, are not provided:
|
||||
##
|
||||
## Parsing also depends on whether the `shortNoVal` and `longNoVal` parameters
|
||||
## are omitted/empty or provided. The details are described in a
|
||||
## `later section<#nimshortnoval-and-nimlongnoval>`_.
|
||||
##
|
||||
## The following syntax is supported:
|
||||
##
|
||||
## 1. Short options: `-a:5`, `-b=5`, `-cde`, `-fgh=5`
|
||||
## 1. Short options: `-abcd`, `-e:5`, `-e=5`
|
||||
## 2. Long options: `--foo:bar`, `--foo=bar`, `--foo`
|
||||
## 3. Arguments: everything that does not start with a `-`
|
||||
##
|
||||
## Passing values to options **requires** a separator (`:`/`=`), short options
|
||||
## (flags) can be bundled together and the last one can take a value.
|
||||
## These three kinds of tokens are enumerated in the
|
||||
## `CmdLineKind enum<#CmdLineKind>`_.
|
||||
##
|
||||
## Option values can begin with the separator character (`:`/`=`), so all of the
|
||||
## following is valid:
|
||||
## - option `foo`, value `:`: `--foo::`, `--foo=:`
|
||||
## - option `foo`, value `=`: `--foo:=`, `--foo==`
|
||||
## When option values begin with ':' or '=', they need to be doubled up (as in
|
||||
## `--delim::`) or alternated (as in `--delim=:`).
|
||||
##
|
||||
## The `--` option, commonly used to denote that every token that follows is
|
||||
## an argument, is interpreted as a long option, and its name is the empty
|
||||
## string. Trailing arguments can be accessed with `remainingArgs<#remainingArgs,OptParser>`_
|
||||
## or `cmdLineRest<#cmdLineRest,OptParser>`_.
|
||||
## string.
|
||||
##
|
||||
## Parsing
|
||||
## =======
|
||||
##
|
||||
## To parse command line options, use the `getopt iterator<#getopt.i,OptParser>`_.
|
||||
## It initializes the `OptParser<#OptParser>`_ object internally and iterates
|
||||
## through the command line options.
|
||||
## Use an `OptParser<#OptParser>`_ to parse command line options. It can be
|
||||
## created with `initOptParser<#initOptParser,string,set[char],seq[string]>`_,
|
||||
## and `next<#next,OptParser>`_ advances the parser by one token.
|
||||
##
|
||||
## For each token, the parser's `kind` (`CmdLineKind enum<#CmdLineKind>`_.),
|
||||
## `key`, and `val` fields are yielded.
|
||||
##
|
||||
## For long and short options, `key` is the option's name, and `val` is either
|
||||
## the option's value, if given, or an empty string. For arguments, the `key`
|
||||
## field contains the argument itself, and `val` is unused (empty).
|
||||
## For each token, the parser's `kind`, `key`, and `val` fields give
|
||||
## information about that token. If the token is a long or short option, `key`
|
||||
## is the option's name, and `val` is either the option's value, if provided,
|
||||
## or the empty string. For arguments, the `key` field contains the argument
|
||||
## itself, and `val` is unused. To check if the end of the command line has
|
||||
## been reached, check if `kind` is equal to `cmdEnd`.
|
||||
##
|
||||
## Here is an example:
|
||||
##
|
||||
runnableExamples:
|
||||
import std/os
|
||||
|
||||
let cmds = "-ab -e:5 --foo --bar=20 file.txt".parseCmdLine()
|
||||
var output: seq[string] = @[]
|
||||
# If cmds is not supplied, real arguments will be retrieved by the `os` module
|
||||
for kind, key, val in getopt(cmds):
|
||||
case kind
|
||||
of cmdEnd: break
|
||||
of cmdShortOption, cmdLongOption:
|
||||
if val == "":
|
||||
output.add("Option: " & key)
|
||||
else:
|
||||
output.add("Option and value: " & key & ", " & val)
|
||||
of cmdArgument:
|
||||
output.add("Argument: " & key)
|
||||
|
||||
doAssert output == @[
|
||||
"Option: a",
|
||||
"Option: b",
|
||||
"Option and value: e, 5",
|
||||
"Option: foo",
|
||||
"Option and value: bar, 20",
|
||||
"Argument: file.txt"
|
||||
]
|
||||
## ```Nim
|
||||
## import std/parseopt
|
||||
##
|
||||
## The `OptParser<#OptParser>`_ can be initialized with
|
||||
## `initOptParser<#initOptParser,string,set[char],seq[string]>`_.
|
||||
## The `next<#next,OptParser>`_ proc advances the parser by one token.
|
||||
## var p = initOptParser("-ab -e:5 --foo --bar=20 file.txt")
|
||||
## while true:
|
||||
## p.next()
|
||||
## case p.kind
|
||||
## of cmdEnd: break
|
||||
## of cmdShortOption, cmdLongOption:
|
||||
## if p.val == "":
|
||||
## echo "Option: ", p.key
|
||||
## else:
|
||||
## echo "Option and value: ", p.key, ", ", p.val
|
||||
## of cmdArgument:
|
||||
## echo "Argument: ", p.key
|
||||
##
|
||||
## When iterating the object manually with `next<#next,OptParser>`_, reaching
|
||||
## the end of the command line is signalled by setting the `kind` field
|
||||
## to `cmdEnd`.
|
||||
## # Output:
|
||||
## # Option: a
|
||||
## # Option: b
|
||||
## # Option and value: e, 5
|
||||
## # Option: foo
|
||||
## # Option and value: bar, 20
|
||||
## # Argument: file.txt
|
||||
## ```
|
||||
##
|
||||
## To set a default value for an option, assign the default value to a variable
|
||||
## beforehand, then update it while parsing.
|
||||
## The `getopt iterator<#getopt.i,OptParser>`_, which is provided for
|
||||
## convenience, can be used to iterate through all command line options as well.
|
||||
##
|
||||
runnableExamples:
|
||||
import std/strutils
|
||||
|
||||
var varName: string = "defaultValue"
|
||||
|
||||
for kind, key, val in getopt(@["--varName:HELLO"]):
|
||||
case kind
|
||||
of cmdArgument:
|
||||
discard
|
||||
of cmdLongOption, cmdShortOption:
|
||||
case key
|
||||
of "varName": # --varName:<value> in the console when executing
|
||||
varName = val.toLowerAscii() # do input sanitization in production
|
||||
of cmdEnd:
|
||||
discard
|
||||
|
||||
doAssert varName == "hello"
|
||||
## To set a default value for a variable assigned through `getopt` and accept arguments from the cmd line.
|
||||
## Assign the default value to a variable before parsing.
|
||||
## Then set the variable to the new value while parsing.
|
||||
##
|
||||
## Here is an example:
|
||||
##
|
||||
## ```Nim
|
||||
## import std/parseopt
|
||||
##
|
||||
## var varName: string = "defaultValue"
|
||||
##
|
||||
## for kind, key, val in getopt():
|
||||
## case kind
|
||||
## of cmdArgument:
|
||||
## discard
|
||||
## of cmdLongOption, cmdShortOption:
|
||||
## case key:
|
||||
## of "varName": # --varName:<value> in the console when executing
|
||||
## varName = val # do input sanitization in production systems
|
||||
## of cmdEnd:
|
||||
## discard
|
||||
## ```
|
||||
##
|
||||
## `shortNoVal` and `longNoVal`
|
||||
## ============================
|
||||
##
|
||||
## The optional `shortNoVal` and `longNoVal` parameters in
|
||||
## `initOptParser<#initOptParser,string,set[char],seq[string]>`_ and
|
||||
## `getopt iterator<#getopt.i,OptParser>`_ are for
|
||||
## The optional `shortNoVal` and `longNoVal` parameters present in
|
||||
## `initOptParser<#initOptParser,string,set[char],seq[string]>`_ are for
|
||||
## specifying which short and long options do not accept values.
|
||||
##
|
||||
## When `shortNoVal` or `longNoVal` is non-empty, using the separators (`:`/`=`)
|
||||
## becomes non-mandatory and users can separate a value from long
|
||||
## options (that are not supplied to the corresponding argument) by whitespace
|
||||
## or, in the case of a short option, by writing the value directly adjacent to
|
||||
## the option.
|
||||
##
|
||||
## For short options, `-j4` becomes supported syntax (parsed as option `j` with
|
||||
## value `4` instead of two separate options `j` and `4`). For long options,
|
||||
## `--foo bar` becomes supported syntax in all `modes<Parser Modes>`_.
|
||||
##
|
||||
## In `LaxMode` and `GnuMode`, short options can also take values from the next
|
||||
## argument (`-c val`), but this does **not** work in the default `Nim` mode.
|
||||
## When `shortNoVal` is non-empty, users are not required to separate short
|
||||
## options and their values with a ':' or '=' since the parser knows which
|
||||
## options accept values and which ones do not. This behavior also applies for
|
||||
## long options if `longNoVal` is non-empty. For short options, `-j4`
|
||||
## becomes supported syntax, and for long options, `--foo bar` becomes
|
||||
## supported. This is in addition to the `previously mentioned
|
||||
## syntax<#supported-syntax>`_. Users can still separate options and their
|
||||
## values with ':' or '=', but that becomes optional.
|
||||
##
|
||||
## As more options which do not accept values are added to your program,
|
||||
## remember to amend `shortNoVal` and `longNoVal` accordingly.
|
||||
##
|
||||
## The parser does not validate the input for syntax mistakes, thus, options
|
||||
## can still have values if passed explicitly by the user, even when they are
|
||||
## marked as `shortNoVal`/`longNoVal`.
|
||||
##
|
||||
## This behavior allows associating an option with the mistakenly passed value:
|
||||
##
|
||||
runnableExamples:
|
||||
import std/[sequtils, os]
|
||||
|
||||
let cmds = "-n:9 --foo:bar".parseCmdLine()
|
||||
let parsed = toSeq(cmds.getopt(shortNoVal = {'n'}, longNoVal = @["foo"]))
|
||||
for (kind, key, val) in parsed:
|
||||
case kind
|
||||
of cmdEnd: raise newException(AssertionDefect, "Unreachable")
|
||||
of cmdShortOption, cmdLongOption:
|
||||
if key in ["n", "foo"] and val != "":
|
||||
# Substitute for proper error handling in your code
|
||||
discard "Option " & key & " can't take values!"
|
||||
else: discard
|
||||
of cmdArgument: discard
|
||||
doAssert parsed == @[
|
||||
(cmdShortOption, "n", "9"),
|
||||
(cmdLongOption, "foo", "bar")]
|
||||
##
|
||||
## .. Important::
|
||||
## Next-argument value-taking for short/long options is only enabled when
|
||||
## `shortNoVal`/`longNoVal` are non-empty. If your program has *no* options
|
||||
## that take no value, you still must pass a non-empty placeholder (for example,
|
||||
## `shortNoVal = {'\0'}` and/or `longNoVal = @[""]`) to enable this form.
|
||||
##
|
||||
## The following example illustrates the difference between having an empty
|
||||
## `shortNoVal` and `longNoVal`, which is the default, and providing
|
||||
## arguments for those two parameters:
|
||||
##
|
||||
runnableExamples:
|
||||
|
||||
proc format(kind: CmdLineKind; key, val: string): string =
|
||||
case kind
|
||||
of cmdEnd: raise newException(AssertionDefect, "Unreachable")
|
||||
of cmdShortOption, cmdLongOption:
|
||||
if val == "": "Option: " & key
|
||||
else: "Option and value: " & key & ", " & val
|
||||
of cmdArgument: "Argument: " & key
|
||||
|
||||
let cmdLine = "-j4 --first bar"
|
||||
var output1, output2: seq[string] = @[]
|
||||
|
||||
var emptyNoVal = initOptParser(cmdLine)
|
||||
for kind, key, val in emptyNoVal.getopt():
|
||||
output1.add format(kind, key, val)
|
||||
|
||||
doAssert output1 == @[
|
||||
"Option: j",
|
||||
"Option: 4",
|
||||
"Option: first",
|
||||
"Argument: bar"
|
||||
]
|
||||
|
||||
var withNoVal = cmdLine.initOptParser(shortNoVal = {'c'},
|
||||
longNoVal = @["second"])
|
||||
for kind, key, val in withNoVal.getopt():
|
||||
output2.add format(kind, key, val)
|
||||
|
||||
doAssert output2 == @[
|
||||
"Option and value: j, 4",
|
||||
"Option and value: first, bar"
|
||||
]
|
||||
## ```Nim
|
||||
## import std/parseopt
|
||||
##
|
||||
## Parser Modes
|
||||
## ============
|
||||
## proc printToken(kind: CmdLineKind, key: string, val: string) =
|
||||
## case kind
|
||||
## of cmdEnd: doAssert(false) # Doesn't happen with getopt()
|
||||
## of cmdShortOption, cmdLongOption:
|
||||
## if val == "":
|
||||
## echo "Option: ", key
|
||||
## else:
|
||||
## echo "Option and value: ", key, ", ", val
|
||||
## of cmdArgument:
|
||||
## echo "Argument: ", key
|
||||
##
|
||||
## .. Warning:: Modes other than the default (`Nim`) are **experimental** and may
|
||||
## change in future releases.
|
||||
## let cmdLine = "-j4 --first bar"
|
||||
##
|
||||
## The parser supports several distinct rule sets that change how options are
|
||||
## interpreted:
|
||||
## var emptyNoVal = initOptParser(cmdLine)
|
||||
## for kind, key, val in emptyNoVal.getopt():
|
||||
## printToken(kind, key, val)
|
||||
##
|
||||
## 1. **LaxMode**: Most forgiving mode, combines `Nim` with POSIX-like
|
||||
## short option handling. Tries to follow the POSIX_ guidelines where possible.
|
||||
## 2. **NimMode**: Standard Nim parsing rules (default).
|
||||
## 3. **GnuMode**: GNU-inspired parsing (e.g. `=` as the only delimiter).
|
||||
## Puts some additional restrictions, following some of the GNU_ conventions.
|
||||
## # Output:
|
||||
## # Option: j
|
||||
## # Option: 4
|
||||
## # Option: first
|
||||
## # Argument: bar
|
||||
##
|
||||
## Modes are ordered from most relaxed to strictest. The names were
|
||||
## chosen to set general user expectations and full compliance is neither
|
||||
## achieved nor planned.
|
||||
## var withNoVal = initOptParser(cmdLine, shortNoVal = {'c'},
|
||||
## longNoVal = @["second"])
|
||||
## for kind, key, val in withNoVal.getopt():
|
||||
## printToken(kind, key, val)
|
||||
##
|
||||
## Mode Differences
|
||||
## ----------------
|
||||
##
|
||||
## **NimMode** (default):
|
||||
##
|
||||
## - Short options require adjacent values or explicit delimiters:
|
||||
## `-cval`, `-c:val`, `-c=val`
|
||||
## - Short options follow POSIX-style bundling rules
|
||||
## - Next-argument value taking (`-c val`) is **not** supported by default
|
||||
## - Supports both `:` and `=` as delimiters
|
||||
## - Allows whitespace around delimiters
|
||||
## - Values starting with `-` are interpreted as new options
|
||||
##
|
||||
## **LaxMode**:
|
||||
##
|
||||
## - Essentially the Nim mode with some relaxations for short options:
|
||||
## + Allows short options to take values from the next argument: `-c val`
|
||||
## + Supports bundled short options with trailing value: `-abc val`
|
||||
## - Values starting with `-` can be consumed as option arguments
|
||||
##
|
||||
## **GnuMode**:
|
||||
##
|
||||
## - Only `=` is treated as a delimiter (`:` is not a delimiter)
|
||||
## - No whitespace allowed around `=`
|
||||
## - Short options can take next-argument values (`-c val`), but only whitespace
|
||||
## is allowed as a delimiter, separators parse as part of the value
|
||||
## - Short options follow POSIX-style bundling rules
|
||||
## - Values starting with `-` can be consumed as option arguments
|
||||
## - Known discrepancies compared to GNU getopt:
|
||||
## + No notion of optional/mandatory arguments, colon (`:`) doesn't
|
||||
## indicate them and overall is not a special character.
|
||||
##
|
||||
## Mode-Specific Behavior
|
||||
## ----------------------
|
||||
##
|
||||
## The parser's behavior varies significantly between modes, particularly
|
||||
## around how options consume their values:
|
||||
##
|
||||
## **Short Options**
|
||||
##
|
||||
## Consider `-c val`:
|
||||
##
|
||||
## - In `Nim` mode: `-c` is parsed as an option without a value, and `val` is
|
||||
## parsed as a separate argument, regardless of `shortNoVal` being empty or not.
|
||||
## - In `Lax` and `Gnu` modes:
|
||||
## + When `shortNoVal` is empty, or not empty and `-c` is in it:
|
||||
## Same as `Nim`, parsed as option `-c` followed by argument `val`.
|
||||
## + When `-c` is not in `shortNoVal`:
|
||||
## parsed as option `-c`, `val` is consumed as its value.
|
||||
##
|
||||
## Consider `-c-10`:
|
||||
##
|
||||
## - If `shortNoVal` value is empty, all three modes parse three separate short
|
||||
## options: `c`, `1` and `0`.
|
||||
## - Otherwise, if `-c` is not in `shortNoVal`:
|
||||
## + `Nim`: `-c` is an option without an argument. `-10` is interpreted as a
|
||||
## an option `-1` with the `0` argument.
|
||||
## + `Lax` and `Gnu` modes: `-10` is consumed as the value of `-c`
|
||||
## (allowing negative number values).
|
||||
##
|
||||
## **Long Options**
|
||||
##
|
||||
## Consider `--foo:bar`:
|
||||
##
|
||||
## - `Nim`: `:` is a valid delimiter, so `bar` is the value of `--foo`.
|
||||
## - `LaxMode`: same as `Nim`.
|
||||
## - `Gnu`: only `=` is a valid delimiter, so this parses as an option named
|
||||
## `foo:bar` without a value (unless `longNoVal` is non-empty and allows
|
||||
## next-argument consumption).
|
||||
##
|
||||
## Consider `--foo =bar`:
|
||||
##
|
||||
## - `Nim`: whitespace around delimiters is allowed, so `=bar` is the
|
||||
## value of `--foo`.
|
||||
## - `LaxMode`: same as `Nim`.
|
||||
## - `Gnu`: whitespace around `=` is not allowed, so `--foo` is an
|
||||
## option without a value, and `=bar` is parsed as an argument.
|
||||
##
|
||||
## Custom Rule Sets
|
||||
## ================
|
||||
##
|
||||
## .. Warning:: Custom rule sets are unsupported and not tested
|
||||
##
|
||||
## If you require parsing rules beyond the three provided modes, it's possible
|
||||
## to define a custom parser behavior by specifying a set of individual parser
|
||||
## rules.
|
||||
##
|
||||
## Due to this feature being unsupported, it requires importing the private
|
||||
## symbols of the module (with `import std/parseopt {.all.}`) and utilizing
|
||||
## the unexported `initOptParser` overload, which accepts `set[ParserRules]`
|
||||
## (see the `ParserRules` enum in the code for details).
|
||||
## # Output:
|
||||
## # Option and value: j, 4
|
||||
## # Option and value: first, bar
|
||||
## ```
|
||||
##
|
||||
## See also
|
||||
## ========
|
||||
@@ -328,42 +171,13 @@ runnableExamples:
|
||||
## parser
|
||||
## * `parsexml module<parsexml.html>`_ for a XML / HTML parser
|
||||
## * `other parsers<lib.html#pure-libraries-parsers>`_ for more parsers
|
||||
## * POSIX_ - The Open Group Base Specifications Issue 8. Utility Conventions
|
||||
## * GNU_ - GNU C Library reference manual. 26.1.1 Program Argument Syntax Conventions
|
||||
##
|
||||
## .. _GNU: https://sourceware.org/glibc/manual/latest/html_node/Argument-Syntax.html
|
||||
## .. _POSIX: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap12.html
|
||||
|
||||
{.push debugger: off.}
|
||||
|
||||
include "system/inclrtl"
|
||||
|
||||
import std/strutils
|
||||
import std/os
|
||||
when defined(nimscript):
|
||||
from std/strutils import toLowerAscii, endsWith
|
||||
|
||||
type
|
||||
CliMode* = enum
|
||||
## Parser behavior profiles used to control parser behavior.
|
||||
## See `Parser Modes`_ for details.
|
||||
LaxMode, ## The most forgiving mode
|
||||
NimMode, ## Nim parsing rules (default)
|
||||
GnuMode ## GNU-style parsing
|
||||
|
||||
type
|
||||
ParserRules = enum
|
||||
## Feature flags used to assemble parser behavior for a given mode.
|
||||
prSepAllowDelimBefore, ## Allow whitespace before an opt-val separator
|
||||
prSepAllowDelimAfter, ## Allow whitespace after an opt-val separator
|
||||
prShortAllowSep, ## Allow `-k<separator>val` form
|
||||
prShortBundle, ## Allow bundling short options behind one '-'
|
||||
prShortValAllowAdjacent, ## Allow adjacent short option values: `-kval`
|
||||
prShortValAllowNextArg, ## Allow next-argv short option values: `-k val`
|
||||
prShortValAllowDashLeading, ## Allow values that start with '-' to be taken
|
||||
prLongAllowSep, ## Allow `--opt<separator>val` form
|
||||
prLongValAllowNextArg, ## Allow `--opt val` form, requires non-empty `longNoVal`
|
||||
prSepAllowColon, ## Allow `:` as an opt-val separator
|
||||
prSepAllowEq, ## Allow `=` as an opt-val separator
|
||||
|
||||
type
|
||||
CmdLineKind* = enum ## The detected command line token.
|
||||
@@ -375,51 +189,21 @@ type
|
||||
## Implementation of the command line parser.
|
||||
##
|
||||
## To initialize it, use the
|
||||
## `initOptParser proc<#initOptParser,string,set[char],seq[string],CliMode>`_.
|
||||
## `next<#next,OptParser>`_ is used to advance the parser state and move
|
||||
## through the parsed tokens.
|
||||
## `initOptParser proc<#initOptParser,string,set[char],seq[string]>`_.
|
||||
pos: int
|
||||
inShortState: bool
|
||||
allowWhitespaceAfterColon: bool
|
||||
shortNoVal: set[char]
|
||||
longNoVal: seq[string]
|
||||
cmds: seq[string]
|
||||
idx: int
|
||||
separators: set[char] ## Allowed separators for long/short option values
|
||||
rules: set[ParserRules]
|
||||
kind*: CmdLineKind ## The detected command line token
|
||||
key*, val*: string ## Key and value pair; the key is the option
|
||||
## or the argument, and the value is not "" if
|
||||
## the option was given a value
|
||||
|
||||
const DelimSet = {'\t', ' '} ## Allowed delimiters between tokens
|
||||
|
||||
func toRules(m: CliMode): set[ParserRules] =
|
||||
## Default rule sets for the given mode `m`
|
||||
let
|
||||
Common = {
|
||||
prSepAllowEq,
|
||||
prShortValAllowAdjacent,
|
||||
prShortBundle,
|
||||
prLongValAllowNextArg,
|
||||
prLongAllowSep,
|
||||
}
|
||||
Lax = {
|
||||
prSepAllowColon,
|
||||
prSepAllowDelimBefore,
|
||||
prSepAllowDelimAfter,
|
||||
prShortAllowSep,
|
||||
}
|
||||
ShortPosix = {
|
||||
prShortValAllowNextArg,
|
||||
prShortValAllowDashLeading,
|
||||
}
|
||||
case m
|
||||
of LaxMode: Common + Lax + ShortPosix
|
||||
of NimMode: Common + Lax
|
||||
of GnuMode: Common + ShortPosix
|
||||
|
||||
proc parseWord(s: string, i: int, w: var string,
|
||||
delim: set[char] = DelimSet): int =
|
||||
delim: set[char] = {'\t', ' '}): int =
|
||||
result = i
|
||||
if result < s.len and s[result] == '\"':
|
||||
inc(result)
|
||||
@@ -434,23 +218,34 @@ proc parseWord(s: string, i: int, w: var string,
|
||||
add(w, s[result])
|
||||
inc(result)
|
||||
|
||||
proc initOptParser(cmdline: openArray[string];
|
||||
shortNoVal: set[char];
|
||||
longNoVal: seq[string];
|
||||
rules: set[ParserRules]): OptParser =
|
||||
result = OptParser(pos: 0, idx: 0,
|
||||
cmds: @cmdline,
|
||||
inShortState: false,
|
||||
shortNoVal: shortNoVal,
|
||||
longNoVal: longNoVal,
|
||||
separators: {},
|
||||
rules: rules,
|
||||
kind: cmdEnd,
|
||||
key: "", val: "",
|
||||
proc initOptParser*(cmdline: seq[string], shortNoVal: set[char] = {},
|
||||
longNoVal: seq[string] = @[];
|
||||
allowWhitespaceAfterColon = true): OptParser =
|
||||
## Initializes the command line parser.
|
||||
##
|
||||
## If `cmdline.len == 0`, the real command line as provided by the
|
||||
## `os` module is retrieved instead if it is available. If the
|
||||
## command line is not available, a `ValueError` will be raised.
|
||||
## Behavior of the other parameters remains the same as in
|
||||
## `initOptParser(string, ...)
|
||||
## <#initOptParser,string,set[char],seq[string]>`_.
|
||||
##
|
||||
## See also:
|
||||
## * `getopt iterator<#getopt.i,seq[string],set[char],seq[string]>`_
|
||||
runnableExamples:
|
||||
var p = initOptParser()
|
||||
p = initOptParser(@["--left", "--debug:3", "-l", "-r:2"])
|
||||
p = initOptParser(@["--left", "--debug:3", "-l", "-r:2"],
|
||||
shortNoVal = {'l'}, longNoVal = @["left"])
|
||||
result = OptParser(pos: 0, idx: 0, inShortState: false,
|
||||
shortNoVal: shortNoVal, longNoVal: longNoVal,
|
||||
allowWhitespaceAfterColon: allowWhitespaceAfterColon
|
||||
)
|
||||
if prSepAllowEq in rules: result.separators.incl('=')
|
||||
if prSepAllowColon in rules: result.separators.incl(':')
|
||||
if cmdline.len == 0:
|
||||
if cmdline.len != 0:
|
||||
result.cmds = newSeq[string](cmdline.len)
|
||||
for i in 0..<cmdline.len:
|
||||
result.cmds[i] = cmdline[i]
|
||||
else:
|
||||
when declared(paramCount):
|
||||
when defined(nimscript):
|
||||
var ctr = 0
|
||||
@@ -459,7 +254,7 @@ proc initOptParser(cmdline: openArray[string];
|
||||
if firstNimsFound:
|
||||
result.cmds[ctr] = paramStr(i)
|
||||
inc ctr, 1
|
||||
if paramStr(i).toLowerAscii().endsWith(".nims") and not firstNimsFound:
|
||||
if paramStr(i).endsWith(".nims") and not firstNimsFound:
|
||||
firstNimsFound = true
|
||||
result.cmds = newSeq[string](paramCount()-i)
|
||||
else:
|
||||
@@ -471,73 +266,25 @@ proc initOptParser(cmdline: openArray[string];
|
||||
# access the command line arguments then!
|
||||
raiseAssert "empty command line given but" &
|
||||
" real command line is not accessible"
|
||||
result.kind = cmdEnd
|
||||
result.key = ""
|
||||
result.val = ""
|
||||
|
||||
proc initOptParser*(cmdline: seq[string];
|
||||
shortNoVal: set[char] = {};
|
||||
proc initOptParser*(cmdline = "", shortNoVal: set[char] = {},
|
||||
longNoVal: seq[string] = @[];
|
||||
mode: CliMode = NimMode): OptParser =
|
||||
allowWhitespaceAfterColon = true): OptParser =
|
||||
## Initializes the command line parser.
|
||||
##
|
||||
## **Parameters:**
|
||||
## If `cmdline == ""`, the real command line as provided by the
|
||||
## `os` module is retrieved instead if it is available. If the
|
||||
## command line is not available, a `ValueError` will be raised.
|
||||
##
|
||||
## - `cmdline`: Sequence of command line arguments to parse. If empty, the
|
||||
## real command line as provided by the `os` module is retrieved instead.
|
||||
## If the command line is not available, an assertion will be raised.
|
||||
## - `shortNoVal`: Set of short option characters that do not accept values.
|
||||
## See `shortNoVal and longNoVal<#nimshortnoval-and-nimlongnoval>`_ for details.
|
||||
## - `longNoVal`: Sequence of long option names that do not accept values.
|
||||
## See `shortNoVal and longNoVal<#nimshortnoval-and-nimlongnoval>`_ for details.
|
||||
## - `mode`: Parser behavior profile (`NimMode`, `LaxMode`, or `GnuMode`).
|
||||
## See `Parser Modes`_ for details.
|
||||
## `shortNoVal` and `longNoVal` are used to specify which options
|
||||
## do not take values. See the `documentation about these
|
||||
## parameters<#nimshortnoval-and-nimlongnoval>`_ for more information on
|
||||
## how this affects parsing.
|
||||
##
|
||||
## See also:
|
||||
## * `getopt iterator<#getopt.i,seq[string],set[char],seq[string],CliMode>`_
|
||||
runnableExamples:
|
||||
var p = initOptParser()
|
||||
p = initOptParser(@["--left", "--debug:3", "-l", "-r:2"])
|
||||
p = initOptParser(@["--left", "--debug:3", "-l", "-r:2"],
|
||||
shortNoVal = {'l'}, longNoVal = @["left"])
|
||||
initOptParser(cmdline, shortNoVal, longNoVal, toRules(mode))
|
||||
|
||||
proc initOptParser*(cmdline: seq[string],
|
||||
shortNoVal: set[char] = {},
|
||||
longNoVal: seq[string] = @[];
|
||||
allowWhitespaceAfterColon: bool): OptParser {.deprecated:
|
||||
"`allowWhitespaceAfterColon` is deprecated, use parser modes instead".} =
|
||||
## This is an overload for continued support of the legacy `allowWhitespaceAfterColon`
|
||||
## option. It modifies the default parser mode so that the passed value is respected.
|
||||
##
|
||||
## Current default parser mode behaves as if `true` was passed (old default)
|
||||
##
|
||||
## - `allowWhitespaceAfterColon`: When `true`, allows forms like
|
||||
## `--option: value` or `--option= value` where the value is in the next
|
||||
## token after the delimiter. When `false`, the value must be in the same
|
||||
## token as the delimiter.
|
||||
var nimrules = toRules(NimMode)
|
||||
if allowWhitespaceAfterColon == false: nimrules.excl prSepAllowDelimAfter
|
||||
initOptParser(cmdline, shortNoVal, longNoVal, nimrules)
|
||||
|
||||
proc initOptParser*(cmdline = "";
|
||||
shortNoVal: set[char] = {};
|
||||
longNoVal: seq[string] = @[];
|
||||
mode: CliMode = NimMode): OptParser =
|
||||
## Initializes the command line parser from a command line string.
|
||||
##
|
||||
## The `cmdline` string is parsed into tokens using shell-like quoting rules.
|
||||
##
|
||||
## **Parameters:**
|
||||
##
|
||||
## - `cmdline`: Command line string to parse. If empty, the real command line
|
||||
## as provided by the `os` module is retrieved instead. If the command line
|
||||
## is not available, an assertion will be raised.
|
||||
## - `shortNoVal`: Set of short option characters that do not accept values.
|
||||
## See `shortNoVal and longNoVal<#nimshortnoval-and-nimlongnoval>`_ for details.
|
||||
## - `longNoVal`: Sequence of long option names that do not accept values.
|
||||
## See `shortNoVal and longNoVal<#nimshortnoval-and-nimlongnoval>`_ for details.
|
||||
## - `mode`: Parser behavior profile (`NimMode`, `LaxMode`, or `GnuMode`).
|
||||
## See `Parser Modes`_ for details.
|
||||
##
|
||||
## **Note:** This does not provide a way of passing default values to arguments.
|
||||
## This does not provide a way of passing default values to arguments.
|
||||
##
|
||||
## See also:
|
||||
## * `getopt iterator<#getopt.i,OptParser>`_
|
||||
@@ -546,81 +293,34 @@ proc initOptParser*(cmdline = "";
|
||||
p = initOptParser("--left --debug:3 -l -r:2")
|
||||
p = initOptParser("--left --debug:3 -l -r:2",
|
||||
shortNoVal = {'l'}, longNoVal = @["left"])
|
||||
initOptParser(parseCmdLine(cmdline), shortNoVal, longNoVal, toRules(mode))
|
||||
|
||||
proc initOptParser*(cmdline = "";
|
||||
shortNoVal: set[char] = {};
|
||||
longNoVal: seq[string] = @[];
|
||||
allowWhitespaceAfterColon: bool): OptParser {.deprecated:
|
||||
"`allowWhitespaceAfterColon` is deprecated, use parser modes instead".} =
|
||||
## This is an overload for continued support of the legacy `allowWhitespaceAfterColon`
|
||||
## option. It modifies the default parser mode so that the passed value is respected.
|
||||
##
|
||||
## Current default parser mode behaves as if `true` was passed (old default).
|
||||
##
|
||||
## - `allowWhitespaceAfterColon`: When `true`, allows forms like
|
||||
## `--option: value` or `--option= value` where the value is in the next
|
||||
## token after the delimiter. When `false`, the value must be in the same
|
||||
## token as the delimiter.
|
||||
var nimrules = toRules(NimMode)
|
||||
if allowWhitespaceAfterColon == false: nimrules.excl prSepAllowDelimAfter
|
||||
initOptParser(parseCmdLine(cmdline), shortNoVal, longNoVal, nimrules)
|
||||
initOptParser(parseCmdLine(cmdline), shortNoVal, longNoVal, allowWhitespaceAfterColon)
|
||||
|
||||
proc handleShortOption(p: var OptParser; cmd: string) =
|
||||
var i = p.pos
|
||||
p.kind = cmdShortOption
|
||||
if i < cmd.len: # multidigit short option support goes here
|
||||
if i < cmd.len:
|
||||
add(p.key, cmd[i])
|
||||
inc(i)
|
||||
p.inShortState = true
|
||||
if prSepAllowDelimBefore in p.rules:
|
||||
while i < cmd.len and cmd[i] in DelimSet:
|
||||
while i < cmd.len and cmd[i] in {'\t', ' '}:
|
||||
inc(i)
|
||||
p.inShortState = false
|
||||
if i < cmd.len and (cmd[i] in {':', '='} or
|
||||
card(p.shortNoVal) > 0 and p.key[0] notin p.shortNoVal):
|
||||
if i < cmd.len and cmd[i] in {':', '='}:
|
||||
inc(i)
|
||||
p.inShortState = false
|
||||
|
||||
proc consumeDelims() =
|
||||
while i < cmd.len and cmd[i] in DelimSet: inc(i)
|
||||
|
||||
proc advance(p: var OptParser; n = 1)=
|
||||
p.inShortState = false
|
||||
while i < cmd.len and cmd[i] in {'\t', ' '}: inc(i)
|
||||
p.val = substr(cmd, i)
|
||||
p.pos = 0
|
||||
inc p.idx
|
||||
else:
|
||||
p.pos = i
|
||||
if i >= cmd.len:
|
||||
p.inShortState = false
|
||||
p.pos = 0
|
||||
inc p.idx, n
|
||||
|
||||
template next(): untyped = p.cmds[p.idx + 1]
|
||||
|
||||
let canTakeVal = card(p.shortNoVal) > 0 and p.key[0] notin p.shortNoVal
|
||||
if i < cmd.len and cmd[i] in p.separators:
|
||||
# separator case
|
||||
if prShortAllowSep in p.rules:
|
||||
# allow separators: skip the separator and take the value after it
|
||||
inc(i)
|
||||
if prSepAllowDelimAfter in p.rules:
|
||||
consumeDelims()
|
||||
# prohibit separators: treat separator + remainder as the value
|
||||
# this represents an error state but produces output that can be validated
|
||||
p.val = substr(cmd, i)
|
||||
p.advance(1)
|
||||
return
|
||||
elif canTakeVal and prShortValAllowAdjacent in p.rules and i < cmd.len:
|
||||
# adjacent value
|
||||
if prSepAllowDelimBefore in p.rules:
|
||||
consumeDelims()
|
||||
p.val = substr(cmd, i)
|
||||
p.advance(1)
|
||||
return
|
||||
elif canTakeVal and
|
||||
prShortValAllowNextArg in p.rules and
|
||||
i >= cmd.len and
|
||||
p.idx + 1 < p.cmds.len and (
|
||||
prShortValAllowDashLeading in p.rules or
|
||||
not (next().len > 0 and next()[0] == '-')):
|
||||
# next-argument value
|
||||
p.val = next()
|
||||
p.advance(2)
|
||||
return
|
||||
p.pos = i
|
||||
if i >= cmd.len:
|
||||
p.advance(1)
|
||||
inc p.idx
|
||||
|
||||
proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
|
||||
## Parses the next token.
|
||||
@@ -643,71 +343,54 @@ proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
|
||||
return
|
||||
|
||||
var i = p.pos
|
||||
template cmd(): untyped = p.cmds[p.idx]
|
||||
template nextArg(): untyped = p.cmds[p.idx + 1]
|
||||
|
||||
proc consumeDelims(cmds: openArray[string]; idx: int) =
|
||||
while i < cmds[idx].len and cmds[idx][i] in DelimSet: inc(i)
|
||||
|
||||
proc advance(p: var OptParser; n = 1) =
|
||||
p.pos = 0
|
||||
inc p.idx, n
|
||||
|
||||
consumeDelims(p.cmds, p.idx)
|
||||
while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i)
|
||||
p.pos = i
|
||||
setLen(p.key, 0)
|
||||
setLen(p.val, 0)
|
||||
if p.inShortState:
|
||||
p.inShortState = false
|
||||
if i < cmd.len:
|
||||
handleShortOption(p, p.cmds[p.idx])
|
||||
return
|
||||
else:
|
||||
p.advance(1)
|
||||
if i >= p.cmds[p.idx].len:
|
||||
inc(p.idx)
|
||||
p.pos = 0
|
||||
if p.idx >= p.cmds.len:
|
||||
p.kind = cmdEnd
|
||||
return
|
||||
else:
|
||||
handleShortOption(p, p.cmds[p.idx])
|
||||
return
|
||||
|
||||
if i < cmd.len and cmd[i] == '-':
|
||||
if i < p.cmds[p.idx].len and p.cmds[p.idx][i] == '-':
|
||||
inc(i)
|
||||
if i < cmd.len and cmd[i] == '-':
|
||||
if i < p.cmds[p.idx].len and p.cmds[p.idx][i] == '-':
|
||||
p.kind = cmdLongOption
|
||||
inc(i)
|
||||
i = parseWord(cmd, i, p.key,
|
||||
DelimSet + (if prLongAllowSep in p.rules: p.separators else: {}))
|
||||
if prSepAllowDelimBefore in p.rules:
|
||||
consumeDelims(p.cmds, p.idx)
|
||||
if prLongAllowSep in p.rules and i < cmd.len and cmd[i] in p.separators:
|
||||
i = parseWord(p.cmds[p.idx], i, p.key, {' ', '\t', ':', '='})
|
||||
while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i)
|
||||
if i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {':', '='}:
|
||||
inc(i)
|
||||
if prSepAllowDelimAfter in p.rules:
|
||||
consumeDelims(p.cmds, p.idx)
|
||||
if i >= cmd.len and p.idx + 1 < p.cmds.len and
|
||||
prSepAllowDelimAfter in p.rules:
|
||||
p.val = nextArg()
|
||||
p.advance(2)
|
||||
else:
|
||||
p.val = cmd.substr(i)
|
||||
p.advance(1)
|
||||
elif prLongValAllowNextArg in p.rules and
|
||||
len(p.longNoVal) > 0 and
|
||||
p.key notin p.longNoVal and
|
||||
p.idx + 1 < p.cmds.len:
|
||||
p.val = nextArg()
|
||||
p.advance(2)
|
||||
while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i)
|
||||
# if we're at the end, use the next command line option:
|
||||
if i >= p.cmds[p.idx].len and p.idx < p.cmds.len and
|
||||
p.allowWhitespaceAfterColon:
|
||||
inc p.idx
|
||||
i = 0
|
||||
if p.idx < p.cmds.len:
|
||||
p.val = p.cmds[p.idx].substr(i)
|
||||
elif len(p.longNoVal) > 0 and p.key notin p.longNoVal and p.idx+1 < p.cmds.len:
|
||||
p.val = p.cmds[p.idx+1]
|
||||
inc p.idx
|
||||
else:
|
||||
if i < cmd.len:
|
||||
# Leave remainder of the current token to be parsed as an argument.
|
||||
consumeDelims(p.cmds, p.idx)
|
||||
p.cmds[p.idx] = cmd.substr(i)
|
||||
else:
|
||||
p.advance(1)
|
||||
p.val = ""
|
||||
inc p.idx
|
||||
p.pos = 0
|
||||
else:
|
||||
p.pos = i
|
||||
handleShortOption(p, cmd)
|
||||
handleShortOption(p, p.cmds[p.idx])
|
||||
else:
|
||||
p.kind = cmdArgument
|
||||
p.key = cmd
|
||||
p.advance(1)
|
||||
p.key = p.cmds[p.idx]
|
||||
inc p.idx
|
||||
p.pos = 0
|
||||
|
||||
when declared(quoteShellCommand):
|
||||
proc cmdLineRest*(p: OptParser): string {.rtl, extern: "npo$1".} =
|
||||
@@ -716,13 +399,15 @@ when declared(quoteShellCommand):
|
||||
## See also:
|
||||
## * `remainingArgs proc<#remainingArgs,OptParser>`_
|
||||
##
|
||||
runnableExamples:
|
||||
var p = initOptParser("--left -r:2 -- foo.txt bar.txt")
|
||||
while true:
|
||||
p.next()
|
||||
if p.kind == cmdLongOption and p.key == "": # Look for "--"
|
||||
break
|
||||
doAssert p.cmdLineRest == "foo.txt bar.txt"
|
||||
## **Examples:**
|
||||
## ```Nim
|
||||
## var p = initOptParser("--left -r:2 -- foo.txt bar.txt")
|
||||
## while true:
|
||||
## p.next()
|
||||
## if p.kind == cmdLongOption and p.key == "": # Look for "--"
|
||||
## break
|
||||
## doAssert p.cmdLineRest == "foo.txt bar.txt"
|
||||
## ```
|
||||
result = p.cmds[p.idx .. ^1].quoteShellCommand
|
||||
|
||||
proc remainingArgs*(p: OptParser): seq[string] {.rtl, extern: "npo$1".} =
|
||||
@@ -731,13 +416,15 @@ proc remainingArgs*(p: OptParser): seq[string] {.rtl, extern: "npo$1".} =
|
||||
## See also:
|
||||
## * `cmdLineRest proc<#cmdLineRest,OptParser>`_
|
||||
##
|
||||
runnableExamples:
|
||||
var p = initOptParser("--left -r:2 -- foo.txt bar.txt")
|
||||
while true:
|
||||
p.next()
|
||||
if p.kind == cmdLongOption and p.key == "": # Look for "--"
|
||||
break
|
||||
doAssert p.remainingArgs == @["foo.txt", "bar.txt"]
|
||||
## **Examples:**
|
||||
## ```Nim
|
||||
## var p = initOptParser("--left -r:2 -- foo.txt bar.txt")
|
||||
## while true:
|
||||
## p.next()
|
||||
## if p.kind == cmdLongOption and p.key == "": # Look for "--"
|
||||
## break
|
||||
## doAssert p.remainingArgs == @["foo.txt", "bar.txt"]
|
||||
## ```
|
||||
result = @[]
|
||||
for i in p.idx..<p.cmds.len: result.add p.cmds[i]
|
||||
|
||||
@@ -752,26 +439,29 @@ iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key,
|
||||
## See also:
|
||||
## * `initOptParser proc<#initOptParser,string,set[char],seq[string]>`_
|
||||
##
|
||||
runnableExamples:
|
||||
# these are placeholders, of course
|
||||
proc writeHelp() = discard
|
||||
proc writeVersion() = discard
|
||||
|
||||
var filename: string = ""
|
||||
var p = initOptParser("--left --debug:3 -l -r:2")
|
||||
|
||||
for kind, key, val in p.getopt():
|
||||
case kind
|
||||
of cmdArgument:
|
||||
filename = key
|
||||
of cmdLongOption, cmdShortOption:
|
||||
case key
|
||||
of "help", "h": writeHelp()
|
||||
of "version", "v": writeVersion()
|
||||
of cmdEnd: assert(false) # cannot happen
|
||||
if filename == "":
|
||||
# no filename has been given, so we show the help
|
||||
writeHelp()
|
||||
## **Examples:**
|
||||
##
|
||||
## ```Nim
|
||||
## # these are placeholders, of course
|
||||
## proc writeHelp() = discard
|
||||
## proc writeVersion() = discard
|
||||
##
|
||||
## var filename: string
|
||||
## var p = initOptParser("--left --debug:3 -l -r:2")
|
||||
##
|
||||
## for kind, key, val in p.getopt():
|
||||
## case kind
|
||||
## of cmdArgument:
|
||||
## filename = key
|
||||
## of cmdLongOption, cmdShortOption:
|
||||
## case key
|
||||
## of "help", "h": writeHelp()
|
||||
## of "version", "v": writeVersion()
|
||||
## of cmdEnd: assert(false) # cannot happen
|
||||
## if filename == "":
|
||||
## # no filename has been given, so we show the help
|
||||
## writeHelp()
|
||||
## ```
|
||||
p.pos = 0
|
||||
p.idx = 0
|
||||
while true:
|
||||
@@ -779,10 +469,8 @@ iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key,
|
||||
if p.kind == cmdEnd: break
|
||||
yield (p.kind, p.key, p.val)
|
||||
|
||||
iterator getopt*(cmdline: seq[string] = @[];
|
||||
shortNoVal: set[char] = {};
|
||||
longNoVal: seq[string] = @[];
|
||||
mode: CliMode = NimMode):
|
||||
iterator getopt*(cmdline: seq[string] = @[],
|
||||
shortNoVal: set[char] = {}, longNoVal: seq[string] = @[]):
|
||||
tuple[kind: CmdLineKind, key, val: string] =
|
||||
## Convenience iterator for iterating over command line arguments.
|
||||
##
|
||||
@@ -795,9 +483,6 @@ iterator getopt*(cmdline: seq[string] = @[];
|
||||
## parameters<#nimshortnoval-and-nimlongnoval>`_ for more information on
|
||||
## how this affects parsing.
|
||||
##
|
||||
## `mode` selects the parser behavior profile (`NimMode`, `LaxMode`,
|
||||
## or `GnuMode`). See `Parser Modes`_ for details.
|
||||
##
|
||||
## There is no need to check for `cmdEnd` while iterating. If using `getopt`
|
||||
## with case switching, checking for `cmdEnd` is required.
|
||||
##
|
||||
@@ -828,8 +513,7 @@ iterator getopt*(cmdline: seq[string] = @[];
|
||||
## writeHelp()
|
||||
## ```
|
||||
var p = initOptParser(cmdline, shortNoVal = shortNoVal,
|
||||
longNoVal = longNoVal,
|
||||
rules = toRules(mode))
|
||||
longNoVal = longNoVal)
|
||||
while true:
|
||||
next(p)
|
||||
if p.kind == cmdEnd: break
|
||||
|
||||
@@ -243,7 +243,7 @@ proc rand[T: uint | uint64](r: var Rand; max: T): T =
|
||||
else:
|
||||
inc iters
|
||||
|
||||
proc rand*(r: var Rand; max: Natural): int {.gcsafe.} =
|
||||
proc rand*(r: var Rand; max: Natural): int {.benign.} =
|
||||
## Returns a random integer in the range `0..max` using the given state.
|
||||
##
|
||||
## **See also:**
|
||||
@@ -260,7 +260,7 @@ proc rand*(r: var Rand; max: Natural): int {.gcsafe.} =
|
||||
cast[int](rand(r, uint64(max)))
|
||||
# xxx toUnsigned pending https://github.com/nim-lang/Nim/pull/18445
|
||||
|
||||
proc rand*(max: int): int {.gcsafe.} =
|
||||
proc rand*(max: int): int {.benign.} =
|
||||
## Returns a random integer in the range `0..max`.
|
||||
##
|
||||
## If `randomize <#randomize>`_ has not been called, the sequence of random
|
||||
@@ -281,7 +281,7 @@ proc rand*(max: int): int {.gcsafe.} =
|
||||
|
||||
rand(state, max)
|
||||
|
||||
proc rand*(r: var Rand; max: range[0.0 .. high(float)]): float {.gcsafe.} =
|
||||
proc rand*(r: var Rand; max: range[0.0 .. high(float)]): float {.benign.} =
|
||||
## Returns a random floating point number in the range `0.0..max`
|
||||
## using the given state.
|
||||
##
|
||||
@@ -308,7 +308,7 @@ proc rand*(r: var Rand; max: range[0.0 .. high(float)]): float {.gcsafe.} =
|
||||
let u = (0x3FFu64 shl 52u64) or (x shr 12u64)
|
||||
result = (cast[float](u) - 1.0) * max
|
||||
|
||||
proc rand*(max: float): float {.gcsafe.} =
|
||||
proc rand*(max: float): float {.benign.} =
|
||||
## Returns a random floating point number in the range `0.0..max`.
|
||||
##
|
||||
## If `randomize <#randomize>`_ has not been called, the sequence of random
|
||||
@@ -612,7 +612,7 @@ proc initRand*(seed: int64): Rand =
|
||||
skipRandomNumbers(result)
|
||||
discard next(result)
|
||||
|
||||
proc randomize*(seed: int64) {.gcsafe.} =
|
||||
proc randomize*(seed: int64) {.benign.} =
|
||||
## Initializes the default random number generator with the given seed.
|
||||
##
|
||||
## Providing a specific seed will produce the same results for that seed each time.
|
||||
@@ -736,7 +736,7 @@ when not defined(standalone):
|
||||
since (1, 5, 1):
|
||||
export initRand
|
||||
|
||||
proc randomize*() {.gcsafe.} =
|
||||
proc randomize*() {.benign.} =
|
||||
## Initializes the default random number generator with a seed based on
|
||||
## random number source.
|
||||
##
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
## stream interface.
|
||||
##
|
||||
## .. warning:: Due to the use of `pointer`, the `readData`, `peekData` and
|
||||
## `writeData` interfaces are not available on the compile-time VM, and must
|
||||
## be cast from a `ptr string` on the JS backend. However, `readDataStr` is
|
||||
## available generally in place of `readData`.
|
||||
## `writeData` interfaces are not available on the compile-time VM, and must
|
||||
## be cast from a `ptr string` on the JS backend. However, `readDataStr` is
|
||||
## available generally in place of `readData`.
|
||||
##
|
||||
## Basic usage
|
||||
## ===========
|
||||
|
||||
@@ -382,9 +382,9 @@ type
|
||||
## timezones. The `times` module only supplies implementations for the
|
||||
## system's local time and UTC.
|
||||
zonedTimeFromTimeImpl: proc (x: Time): ZonedTime
|
||||
{.tags: [], raises: [], gcsafe.}
|
||||
{.tags: [], raises: [], benign.}
|
||||
zonedTimeFromAdjTimeImpl: proc (x: Time): ZonedTime
|
||||
{.tags: [], raises: [], gcsafe.}
|
||||
{.tags: [], raises: [], benign.}
|
||||
name: string
|
||||
|
||||
ZonedTime* = object ## Represents a point in time with an associated
|
||||
@@ -432,7 +432,7 @@ else:
|
||||
# Helper procs
|
||||
#
|
||||
|
||||
{.pragma: operator, rtl, noSideEffect, gcsafe.}
|
||||
{.pragma: operator, rtl, noSideEffect, benign.}
|
||||
|
||||
proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T
|
||||
{.inline.} =
|
||||
@@ -518,7 +518,7 @@ proc fromEpochDay(epochday: int64):
|
||||
return (d.MonthdayRange, m.Month, (y + ord(m <= 2)).int)
|
||||
|
||||
proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int):
|
||||
YeardayRange {.tags: [], raises: [], gcsafe.} =
|
||||
YeardayRange {.tags: [], raises: [], benign.} =
|
||||
## Returns the day of the year.
|
||||
## Equivalent with `dateTime(year, month, monthday, 0, 0, 0, 0).yearday`.
|
||||
runnableExamples:
|
||||
@@ -538,7 +538,7 @@ proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int):
|
||||
result = daysUntilMonth[month] + monthday - 1
|
||||
|
||||
proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay
|
||||
{.tags: [], raises: [], gcsafe.} =
|
||||
{.tags: [], raises: [], benign.} =
|
||||
## Returns the day of the week enum from day, month and year.
|
||||
## Equivalent with `dateTime(year, month, monthday, 0, 0, 0, 0).weekday`.
|
||||
runnableExamples:
|
||||
@@ -922,21 +922,21 @@ proc nanosecond*(time: Time): NanosecondRange =
|
||||
time.nanosecond
|
||||
|
||||
proc fromUnix*(unix: int64): Time
|
||||
{.gcsafe, tags: [], raises: [], noSideEffect.} =
|
||||
{.benign, tags: [], raises: [], noSideEffect.} =
|
||||
## Convert a unix timestamp (seconds since `1970-01-01T00:00:00Z`)
|
||||
## to a `Time`.
|
||||
runnableExamples:
|
||||
doAssert $fromUnix(0).utc == "1970-01-01T00:00:00Z"
|
||||
initTime(unix, 0)
|
||||
|
||||
proc toUnix*(t: Time): int64 {.gcsafe, tags: [], raises: [], noSideEffect.} =
|
||||
proc toUnix*(t: Time): int64 {.benign, tags: [], raises: [], noSideEffect.} =
|
||||
## Convert `t` to a unix timestamp (seconds since `1970-01-01T00:00:00Z`).
|
||||
## See also `toUnixFloat` for subsecond resolution.
|
||||
runnableExamples:
|
||||
doAssert fromUnix(0).toUnix() == 0
|
||||
t.seconds
|
||||
|
||||
proc fromUnixFloat(seconds: float): Time {.gcsafe, tags: [], raises: [], noSideEffect.} =
|
||||
proc fromUnixFloat(seconds: float): Time {.benign, tags: [], raises: [], noSideEffect.} =
|
||||
## Convert a unix timestamp in seconds to a `Time`; same as `fromUnix`
|
||||
## but with subsecond resolution.
|
||||
runnableExamples:
|
||||
@@ -946,7 +946,7 @@ proc fromUnixFloat(seconds: float): Time {.gcsafe, tags: [], raises: [], noSideE
|
||||
let nsecs = (seconds - secs) * 1e9
|
||||
initTime(secs.int64, nsecs.NanosecondRange)
|
||||
|
||||
proc toUnixFloat(t: Time): float {.gcsafe, tags: [], raises: [].} =
|
||||
proc toUnixFloat(t: Time): float {.benign, tags: [], raises: [].} =
|
||||
## Same as `toUnix` but using subsecond resolution.
|
||||
runnableExamples:
|
||||
let t = getTime()
|
||||
@@ -975,7 +975,7 @@ proc toWinTime*(t: Time): int64 =
|
||||
proc getTimeImpl(typ: typedesc[Time]): Time =
|
||||
raiseAssert "implemented in the vm"
|
||||
|
||||
proc getTime*(): Time {.tags: [TimeEffect], gcsafe.} =
|
||||
proc getTime*(): Time {.tags: [TimeEffect], benign.} =
|
||||
## Gets the current time as a `Time` with up to nanosecond resolution.
|
||||
when nimvm:
|
||||
result = getTimeImpl(Time)
|
||||
@@ -1154,7 +1154,7 @@ proc isLeapDay*(dt: DateTime): bool {.since: (1, 1).} =
|
||||
assertDateTimeInitialized dt
|
||||
dt.year.isLeapYear and dt.month == mFeb and dt.monthday == 29
|
||||
|
||||
proc toTime*(dt: DateTime): Time {.tags: [], raises: [], gcsafe.} =
|
||||
proc toTime*(dt: DateTime): Time {.tags: [], raises: [], benign.} =
|
||||
## Converts a `DateTime` to a `Time` representing the same point in time.
|
||||
assertDateTimeInitialized dt
|
||||
let epochDay = toEpochDay(dt.monthday, dt.month, dt.year)
|
||||
@@ -1197,9 +1197,9 @@ proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime =
|
||||
proc newTimezone*(
|
||||
name: string,
|
||||
zonedTimeFromTimeImpl: proc (time: Time): ZonedTime
|
||||
{.tags: [], raises: [], gcsafe.},
|
||||
{.tags: [], raises: [], benign.},
|
||||
zonedTimeFromAdjTimeImpl: proc (adjTime: Time): ZonedTime
|
||||
{.tags: [], raises: [], gcsafe.}
|
||||
{.tags: [], raises: [], benign.}
|
||||
): owned Timezone =
|
||||
## Create a new `Timezone`.
|
||||
##
|
||||
@@ -1263,12 +1263,12 @@ proc `==`*(zone1, zone2: Timezone): bool =
|
||||
zone1.name == zone2.name
|
||||
|
||||
proc inZone*(time: Time, zone: Timezone): DateTime
|
||||
{.tags: [], raises: [], gcsafe.} =
|
||||
{.tags: [], raises: [], benign.} =
|
||||
## Convert `time` into a `DateTime` using `zone` as the timezone.
|
||||
result = initDateTime(zone.zonedTimeFromTime(time), zone)
|
||||
|
||||
proc inZone*(dt: DateTime, zone: Timezone): DateTime
|
||||
{.tags: [], raises: [], gcsafe.} =
|
||||
{.tags: [], raises: [], benign.} =
|
||||
## Returns a `DateTime` representing the same point in time as `dt` but
|
||||
## using `zone` as the timezone.
|
||||
assertDateTimeInitialized dt
|
||||
@@ -1283,14 +1283,14 @@ proc toAdjTime(dt: DateTime): Time =
|
||||
result = initTime(seconds, dt.nanosecond)
|
||||
|
||||
when defined(js):
|
||||
proc localZonedTimeFromTime(time: Time): ZonedTime {.gcsafe.} =
|
||||
proc localZonedTimeFromTime(time: Time): ZonedTime {.benign.} =
|
||||
let jsDate = newDate(time.seconds * 1000)
|
||||
let offset = jsDate.getTimezoneOffset() * secondsInMin
|
||||
result.time = time
|
||||
result.utcOffset = offset
|
||||
result.isDst = false
|
||||
|
||||
proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.gcsafe.} =
|
||||
proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.benign.} =
|
||||
let utcDate = newDate(adjTime.seconds * 1000)
|
||||
let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(),
|
||||
utcDate.getUTCDate(), utcDate.getUTCHours(), utcDate.getUTCMinutes(),
|
||||
@@ -1337,11 +1337,11 @@ else:
|
||||
return ((a.int64 - tm.toAdjUnix).int, tm.tm_isdst > 0)
|
||||
return (0, false)
|
||||
|
||||
proc localZonedTimeFromTime(time: Time): ZonedTime {.gcsafe.} =
|
||||
proc localZonedTimeFromTime(time: Time): ZonedTime {.benign.} =
|
||||
let (offset, dst) = getLocalOffsetAndDst(time.seconds)
|
||||
result = ZonedTime(time: time, utcOffset: offset, isDst: dst)
|
||||
|
||||
proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.gcsafe.} =
|
||||
proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.benign.} =
|
||||
var adjUnix = adjTime.seconds
|
||||
let past = adjUnix - secondsInDay
|
||||
let (pastOffset, _) = getLocalOffsetAndDst(past)
|
||||
@@ -1408,7 +1408,7 @@ proc local*(t: Time): DateTime =
|
||||
## Shorthand for `t.inZone(local())`.
|
||||
t.inZone(local())
|
||||
|
||||
proc now*(): DateTime {.tags: [TimeEffect], gcsafe.} =
|
||||
proc now*(): DateTime {.tags: [TimeEffect], benign.} =
|
||||
## Get the current time as a `DateTime` in the local timezone.
|
||||
## Shorthand for `getTime().local`.
|
||||
##
|
||||
@@ -2327,7 +2327,7 @@ proc parseTime*(input: string, f: static[string], zone: Timezone): Time
|
||||
const f2 = initTimeFormat(f)
|
||||
result = input.parse(f2, zone).toTime()
|
||||
|
||||
proc `$`*(dt: DateTime): string {.tags: [], raises: [], gcsafe.} =
|
||||
proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} =
|
||||
## Converts a `DateTime` object to a string representation.
|
||||
## It uses the format `yyyy-MM-dd'T'HH:mm:sszzz`.
|
||||
runnableExamples:
|
||||
@@ -2339,7 +2339,7 @@ proc `$`*(dt: DateTime): string {.tags: [], raises: [], gcsafe.} =
|
||||
else:
|
||||
result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz")
|
||||
|
||||
proc `$`*(time: Time): string {.tags: [], raises: [], gcsafe.} =
|
||||
proc `$`*(time: Time): string {.tags: [], raises: [], benign.} =
|
||||
## Converts a `Time` value to a string representation. It will use the local
|
||||
## time zone and use the format `yyyy-MM-dd'T'HH:mm:sszzz`.
|
||||
runnableExamples:
|
||||
|
||||
@@ -96,9 +96,6 @@ proc supportsCopyMem*(t: typedesc): bool {.magic: "TypeTrait".}
|
||||
##
|
||||
## Other languages name a type like these `blob`:idx:.
|
||||
|
||||
proc canFormCycles*(t: typedesc): bool {.magic: "TypeTrait".}
|
||||
## Returns true if `t` can form cycles.
|
||||
|
||||
proc hasDefaultValue*(t: typedesc): bool {.magic: "TypeTrait".} =
|
||||
## Returns true if `t` has a valid default value.
|
||||
runnableExamples:
|
||||
|
||||
@@ -338,3 +338,63 @@ proc diffText*(textA, textB: string): seq[Item] =
|
||||
optimize(dataA)
|
||||
optimize(dataB)
|
||||
result = createDiffs(dataA, dataB)
|
||||
|
||||
proc renderDiff*(a, b: string; res: seq[Item]): string =
|
||||
## Renders a diff between two strings as a human-readable unified diff format.
|
||||
##
|
||||
## `a` the original text
|
||||
## `b` the modified text
|
||||
## `res` the sequence of Items from `diffText(a, b)`
|
||||
##
|
||||
## Returns a string with the diff output where:
|
||||
## - Lines prefixed with `-` are deletions from `a`
|
||||
## - Lines prefixed with `+` are insertions in `b`
|
||||
## - Lines prefixed with ` ` are context (unchanged)
|
||||
runnableExamples:
|
||||
let a = "line1\nline2\nline3"
|
||||
let b = "line1\nmodified\nline3"
|
||||
let diff = diffText(a, b)
|
||||
let rendered = renderDiff(a, b, diff)
|
||||
assert "-line2" in rendered
|
||||
assert "+modified" in rendered
|
||||
|
||||
let linesA = a.splitLines
|
||||
let linesB = b.splitLines
|
||||
|
||||
var posA = 0
|
||||
var posB = 0
|
||||
|
||||
for item in res:
|
||||
# Add context lines before this change
|
||||
while posA < item.startA and posB < item.startB:
|
||||
result.add ' '
|
||||
result.add linesA[posA]
|
||||
result.add '\n'
|
||||
inc posA
|
||||
inc posB
|
||||
|
||||
# Add deleted lines from A
|
||||
for i in 0 ..< item.deletedA:
|
||||
result.add '-'
|
||||
result.add linesA[item.startA + i]
|
||||
result.add '\n'
|
||||
|
||||
# Add inserted lines from B
|
||||
for i in 0 ..< item.insertedB:
|
||||
result.add '+'
|
||||
result.add linesB[item.startB + i]
|
||||
result.add '\n'
|
||||
|
||||
posA = item.startA + item.deletedA
|
||||
posB = item.startB + item.insertedB
|
||||
|
||||
# Add remaining context lines after the last change
|
||||
while posA < linesA.len and posB < linesB.len:
|
||||
result.add ' '
|
||||
result.add linesA[posA]
|
||||
result.add '\n'
|
||||
inc posA
|
||||
inc posB
|
||||
|
||||
proc diffOutput*(a, b: string): string =
|
||||
renderDiff(a, b, diffText(a, b))
|
||||
@@ -331,7 +331,7 @@ proc rawRemoveDir(dir: string) {.noWeirdTarget.} =
|
||||
if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError(), dir)
|
||||
|
||||
proc removeDir*(dir: string, checkDir = false) {.rtl, extern: "nos$1", tags: [
|
||||
WriteDirEffect, ReadDirEffect], gcsafe, noWeirdTarget.} =
|
||||
WriteDirEffect, ReadDirEffect], benign, noWeirdTarget.} =
|
||||
## Removes the directory `dir` including all subdirectories and files
|
||||
## in `dir` (recursively).
|
||||
##
|
||||
@@ -441,7 +441,7 @@ proc createDir*(dir: string) {.rtl, extern: "nos$1",
|
||||
discard existsOrCreateDir(p)
|
||||
|
||||
proc copyDir*(source, dest: string, skipSpecial = false) {.rtl, extern: "nos$1",
|
||||
tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect], gcsafe, noWeirdTarget.} =
|
||||
tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect], benign, noWeirdTarget.} =
|
||||
## Copies a directory from `source` to `dest`.
|
||||
##
|
||||
## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks
|
||||
@@ -482,7 +482,7 @@ proc copyDirWithPermissions*(source, dest: string,
|
||||
ignorePermissionErrors = true,
|
||||
skipSpecial = false)
|
||||
{.rtl, extern: "nos$1", tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect],
|
||||
gcsafe, noWeirdTarget.} =
|
||||
benign, noWeirdTarget.} =
|
||||
## Copies a directory from `source` to `dest` preserving file permissions.
|
||||
##
|
||||
## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks
|
||||
|
||||
@@ -182,7 +182,7 @@ proc checkErr(f: File) =
|
||||
|
||||
{.push stackTrace: off, profiler: off.}
|
||||
proc readBuffer*(f: File, buffer: pointer, len: Natural): int {.
|
||||
tags: [ReadIOEffect], gcsafe.} =
|
||||
tags: [ReadIOEffect], benign.} =
|
||||
## Reads `len` bytes into the buffer pointed to by `buffer`. Returns
|
||||
## the actual number of bytes that have been read which may be less than
|
||||
## `len` (if not as many bytes are remaining), but not greater.
|
||||
@@ -191,20 +191,20 @@ proc readBuffer*(f: File, buffer: pointer, len: Natural): int {.
|
||||
|
||||
proc readBytes*(f: File, a: var openArray[int8|uint8], start,
|
||||
len: Natural): int {.
|
||||
tags: [ReadIOEffect], gcsafe.} =
|
||||
tags: [ReadIOEffect], benign.} =
|
||||
## Reads `len` bytes into the buffer `a` starting at `a[start]`. Returns
|
||||
## the actual number of bytes that have been read which may be less than
|
||||
## `len` (if not as many bytes are remaining), but not greater.
|
||||
result = readBuffer(f, addr(a[start]), len)
|
||||
|
||||
proc readChars*(f: File, a: var openArray[char]): int {.tags: [ReadIOEffect], gcsafe.} =
|
||||
proc readChars*(f: File, a: var openArray[char]): int {.tags: [ReadIOEffect], benign.} =
|
||||
## Reads up to `a.len` bytes into the buffer `a`. Returns
|
||||
## the actual number of bytes that have been read which may be less than
|
||||
## `a.len` (if not as many bytes are remaining), but not greater.
|
||||
result = readBuffer(f, addr(a[0]), a.len)
|
||||
|
||||
proc readChars*(f: File, a: var openArray[char], start, len: Natural): int {.
|
||||
tags: [ReadIOEffect], gcsafe, deprecated:
|
||||
tags: [ReadIOEffect], benign, deprecated:
|
||||
"use other `readChars` overload, possibly via: readChars(toOpenArray(buf, start, len-1))".} =
|
||||
## Reads `len` bytes into the buffer `a` starting at `a[start]`. Returns
|
||||
## the actual number of bytes that have been read which may be less than
|
||||
@@ -213,13 +213,13 @@ proc readChars*(f: File, a: var openArray[char], start, len: Natural): int {.
|
||||
raiseEIO("buffer overflow: (start+len) > length of openarray buffer")
|
||||
result = readBuffer(f, addr(a[start]), len)
|
||||
|
||||
proc write*(f: File, c: cstring) {.tags: [WriteIOEffect], gcsafe.} =
|
||||
proc write*(f: File, c: cstring) {.tags: [WriteIOEffect], benign.} =
|
||||
## Writes a value to the file `f`. May throw an IO exception.
|
||||
discard c_fputs(c, f)
|
||||
checkErr(f)
|
||||
|
||||
proc writeBuffer*(f: File, buffer: pointer, len: Natural): int {.
|
||||
tags: [WriteIOEffect], gcsafe.} =
|
||||
tags: [WriteIOEffect], benign.} =
|
||||
## Writes the bytes of buffer pointed to by the parameter `buffer` to the
|
||||
## file `f`. Returns the number of actual written bytes, which may be less
|
||||
## than `len` in case of an error.
|
||||
@@ -227,7 +227,7 @@ proc writeBuffer*(f: File, buffer: pointer, len: Natural): int {.
|
||||
checkErr(f)
|
||||
|
||||
proc writeBytes*(f: File, a: openArray[int8|uint8], start, len: Natural): int {.
|
||||
tags: [WriteIOEffect], gcsafe.} =
|
||||
tags: [WriteIOEffect], benign.} =
|
||||
## Writes the bytes of `a[start..start+len-1]` to the file `f`. Returns
|
||||
## the number of actual written bytes, which may be less than `len` in case
|
||||
## of an error.
|
||||
@@ -235,7 +235,7 @@ proc writeBytes*(f: File, a: openArray[int8|uint8], start, len: Natural): int {.
|
||||
result = writeBuffer(f, addr(x[int(start)]), len)
|
||||
|
||||
proc writeChars*(f: File, a: openArray[char], start, len: Natural): int {.
|
||||
tags: [WriteIOEffect], gcsafe.} =
|
||||
tags: [WriteIOEffect], benign.} =
|
||||
## Writes the bytes of `a[start..start+len-1]` to the file `f`. Returns
|
||||
## the number of actual written bytes, which may be less than `len` in case
|
||||
## of an error.
|
||||
@@ -264,7 +264,7 @@ when defined(windows):
|
||||
break
|
||||
inc i, w
|
||||
|
||||
proc write*(f: File, s: string) {.tags: [WriteIOEffect], gcsafe.} =
|
||||
proc write*(f: File, s: string) {.tags: [WriteIOEffect], benign.} =
|
||||
when defined(windows):
|
||||
writeWindows(f, s, doRaise = true)
|
||||
else:
|
||||
@@ -393,7 +393,7 @@ when defined(nimdoc) or (defined(posix) and not defined(nimscript)) or defined(w
|
||||
inheritable.WinDWORD) != 0
|
||||
|
||||
proc readLine*(f: File, line: var string): bool {.tags: [ReadIOEffect],
|
||||
gcsafe.} =
|
||||
benign.} =
|
||||
## Reads a line of text from the file `f` into `line`. May throw an IO
|
||||
## exception.
|
||||
## A line of text may be delimited by `LF` or `CRLF`. The newline
|
||||
@@ -519,43 +519,43 @@ proc readLine*(f: File, line: var string): bool {.tags: [ReadIOEffect],
|
||||
sp = 128 # read in 128 bytes at a time
|
||||
line.setLen(pos+sp)
|
||||
|
||||
proc readLine*(f: File): string {.tags: [ReadIOEffect], gcsafe.} =
|
||||
proc readLine*(f: File): string {.tags: [ReadIOEffect], benign.} =
|
||||
## Reads a line of text from the file `f`. May throw an IO exception.
|
||||
## A line of text may be delimited by `LF` or `CRLF`. The newline
|
||||
## character(s) are not part of the returned string.
|
||||
result = newStringOfCap(80)
|
||||
if not readLine(f, result): raiseEOF()
|
||||
|
||||
proc write*(f: File, i: int) {.tags: [WriteIOEffect], gcsafe.} =
|
||||
proc write*(f: File, i: int) {.tags: [WriteIOEffect], benign.} =
|
||||
when sizeof(int) == 8:
|
||||
if c_fprintf(f, "%lld", i) < 0: checkErr(f)
|
||||
else:
|
||||
if c_fprintf(f, "%ld", i) < 0: checkErr(f)
|
||||
|
||||
proc write*(f: File, i: BiggestInt) {.tags: [WriteIOEffect], gcsafe.} =
|
||||
proc write*(f: File, i: BiggestInt) {.tags: [WriteIOEffect], benign.} =
|
||||
when sizeof(BiggestInt) == 8:
|
||||
if c_fprintf(f, "%lld", i) < 0: checkErr(f)
|
||||
else:
|
||||
if c_fprintf(f, "%ld", i) < 0: checkErr(f)
|
||||
|
||||
proc write*(f: File, b: bool) {.tags: [WriteIOEffect], gcsafe.} =
|
||||
proc write*(f: File, b: bool) {.tags: [WriteIOEffect], benign.} =
|
||||
if b: write(f, "true")
|
||||
else: write(f, "false")
|
||||
|
||||
proc write*(f: File, r: float32) {.tags: [WriteIOEffect], gcsafe.} =
|
||||
proc write*(f: File, r: float32) {.tags: [WriteIOEffect], benign.} =
|
||||
var buffer {.noinit.}: array[65, char]
|
||||
discard writeFloatToBuffer(buffer, r)
|
||||
if c_fprintf(f, "%s", buffer[0].addr) < 0: checkErr(f)
|
||||
|
||||
proc write*(f: File, r: BiggestFloat) {.tags: [WriteIOEffect], gcsafe.} =
|
||||
proc write*(f: File, r: BiggestFloat) {.tags: [WriteIOEffect], benign.} =
|
||||
var buffer {.noinit.}: array[65, char]
|
||||
discard writeFloatToBuffer(buffer, r)
|
||||
if c_fprintf(f, "%s", buffer[0].addr) < 0: checkErr(f)
|
||||
|
||||
proc write*(f: File, c: char) {.tags: [WriteIOEffect], gcsafe.} =
|
||||
proc write*(f: File, c: char) {.tags: [WriteIOEffect], benign.} =
|
||||
discard c_putc(cint(c), f)
|
||||
|
||||
proc write*(f: File, a: varargs[string, `$`]) {.tags: [WriteIOEffect], gcsafe.} =
|
||||
proc write*(f: File, a: varargs[string, `$`]) {.tags: [WriteIOEffect], benign.} =
|
||||
for x in items(a): write(f, x)
|
||||
|
||||
proc readAllBuffer(file: File): string =
|
||||
@@ -579,7 +579,7 @@ proc rawFileSize(file: File): int64 =
|
||||
result = c_ftell(file)
|
||||
discard c_fseek(file, oldPos, 0)
|
||||
|
||||
proc endOfFile*(f: File): bool {.tags: [], gcsafe.} =
|
||||
proc endOfFile*(f: File): bool {.tags: [], benign.} =
|
||||
## Returns true if `f` is at the end.
|
||||
var c = c_fgetc(f)
|
||||
discard c_ungetc(c, f)
|
||||
@@ -603,7 +603,7 @@ proc readAllFile(file: File): string =
|
||||
var len = rawFileSize(file)
|
||||
result = readAllFile(file, len)
|
||||
|
||||
proc readAll*(file: File): string {.tags: [ReadIOEffect], gcsafe.} =
|
||||
proc readAll*(file: File): string {.tags: [ReadIOEffect], benign.} =
|
||||
## Reads all data from the stream `file`.
|
||||
##
|
||||
## Raises an IO exception in case of an error. It is an error if the
|
||||
@@ -621,7 +621,7 @@ proc readAll*(file: File): string {.tags: [ReadIOEffect], gcsafe.} =
|
||||
result = readAllBuffer(file)
|
||||
|
||||
proc writeLine*[Ty](f: File, x: varargs[Ty, `$`]) {.inline,
|
||||
tags: [WriteIOEffect], gcsafe.} =
|
||||
tags: [WriteIOEffect], benign.} =
|
||||
## Writes the values `x` to `f` and then writes "\\n".
|
||||
## May throw an IO exception.
|
||||
for i in items(x):
|
||||
@@ -713,7 +713,7 @@ when defined(posix) and not defined(nimscript):
|
||||
|
||||
proc open*(f: var File, filename: string,
|
||||
mode: FileMode = fmRead,
|
||||
bufSize: int = -1): bool {.tags: [], raises: [], gcsafe.} =
|
||||
bufSize: int = -1): bool {.tags: [], raises: [], benign.} =
|
||||
## Opens a file named `filename` with given `mode`.
|
||||
##
|
||||
## Default mode is readonly. Returns true if the file could be opened.
|
||||
@@ -747,7 +747,7 @@ proc open*(f: var File, filename: string,
|
||||
result = false
|
||||
|
||||
proc reopen*(f: File, filename: string, mode: FileMode = fmRead): bool {.
|
||||
tags: [], gcsafe.} =
|
||||
tags: [], benign.} =
|
||||
## Reopens the file `f` with given `filename` and `mode`. This
|
||||
## is often used to redirect the `stdin`, `stdout` or `stderr`
|
||||
## file variables.
|
||||
@@ -766,7 +766,7 @@ proc reopen*(f: File, filename: string, mode: FileMode = fmRead): bool {.
|
||||
result = false
|
||||
|
||||
proc open*(f: var File, filehandle: FileHandle,
|
||||
mode: FileMode = fmRead): bool {.tags: [], raises: [], gcsafe.} =
|
||||
mode: FileMode = fmRead): bool {.tags: [], raises: [], benign.} =
|
||||
## Creates a `File` from a `filehandle` with given `mode`.
|
||||
##
|
||||
## Default mode is readonly. Returns true if the file could be opened.
|
||||
@@ -792,26 +792,26 @@ proc open*(filename: string,
|
||||
if not open(result, filename, mode, bufSize):
|
||||
raise newException(IOError, "cannot open: " & filename)
|
||||
|
||||
proc setFilePos*(f: File, pos: int64, relativeTo: FileSeekPos = fspSet) {.gcsafe, sideEffect.} =
|
||||
proc setFilePos*(f: File, pos: int64, relativeTo: FileSeekPos = fspSet) {.benign, sideEffect.} =
|
||||
## Sets the position of the file pointer that is used for read/write
|
||||
## operations. The file's first byte has the index zero.
|
||||
if c_fseek(f, pos, cint(relativeTo)) != 0:
|
||||
raiseEIO("cannot set file position")
|
||||
|
||||
proc getFilePos*(f: File): int64 {.gcsafe.} =
|
||||
proc getFilePos*(f: File): int64 {.benign.} =
|
||||
## Retrieves the current position of the file pointer that is used to
|
||||
## read from the file `f`. The file's first byte has the index zero.
|
||||
result = c_ftell(f)
|
||||
if result < 0: raiseEIO("cannot retrieve file position")
|
||||
|
||||
proc getFileSize*(f: File): int64 {.tags: [ReadIOEffect], gcsafe.} =
|
||||
proc getFileSize*(f: File): int64 {.tags: [ReadIOEffect], benign.} =
|
||||
## Retrieves the file size (in bytes) of `f`.
|
||||
let oldPos = getFilePos(f)
|
||||
discard c_fseek(f, 0, 2) # seek the end of the file
|
||||
result = getFilePos(f)
|
||||
setFilePos(f, oldPos)
|
||||
|
||||
proc setStdIoUnbuffered*() {.tags: [], gcsafe.} =
|
||||
proc setStdIoUnbuffered*() {.tags: [], benign.} =
|
||||
## Configures `stdin`, `stdout` and `stderr` to be unbuffered.
|
||||
when declared(stdout):
|
||||
discard c_setvbuf(stdout, nil, IONBF, 0)
|
||||
@@ -865,7 +865,7 @@ when defined(windows) and appType == "console" and
|
||||
discard setConsoleCP(Utf8codepage)
|
||||
addExitProc(restoreConsoleCP)
|
||||
|
||||
proc readFile*(filename: string): string {.tags: [ReadIOEffect], gcsafe.} =
|
||||
proc readFile*(filename: string): string {.tags: [ReadIOEffect], benign.} =
|
||||
## Opens a file named `filename` for reading, calls `readAll
|
||||
## <#readAll,File>`_ and closes the file afterwards. Returns the string.
|
||||
## Raises an IO exception in case of an error. If you need to call
|
||||
@@ -880,7 +880,7 @@ proc readFile*(filename: string): string {.tags: [ReadIOEffect], gcsafe.} =
|
||||
else:
|
||||
raise newException(IOError, "cannot open: " & filename)
|
||||
|
||||
proc writeFile*(filename, content: string) {.tags: [WriteIOEffect], gcsafe.} =
|
||||
proc writeFile*(filename, content: string) {.tags: [WriteIOEffect], benign.} =
|
||||
## Opens a file named `filename` for writing. Then writes the
|
||||
## `content` completely to the file and closes the file afterwards.
|
||||
## Raises an IO exception in case of an error.
|
||||
|
||||
@@ -219,16 +219,16 @@ elif someVcc:
|
||||
elif mem == ATOMIC_ACQ_REL: fence()
|
||||
elif mem == ATOMIC_SEQ_CST: fence()
|
||||
|
||||
proc atomicStoreN*[T: AtomType](p: ptr T, val: T, mem: static[AtomMemModel]) {.enforcenoraises.} =
|
||||
proc atomicStoreN*[T: AtomType](p: ptr T, val: T, mem: static[AtomMemModel]) =
|
||||
barrier(mem)
|
||||
p[] = val
|
||||
|
||||
proc atomicLoadN*[T: AtomType](p: ptr T, mem: static[AtomMemModel]): T {.enforcenoraises.} =
|
||||
proc atomicLoadN*[T: AtomType](p: ptr T, mem: static[AtomMemModel]): T =
|
||||
result = p[]
|
||||
barrier(mem)
|
||||
|
||||
proc atomicCompareExchangeN*[T: ptr](p, expected: ptr T, desired: T,
|
||||
weak: bool, success_memmodel: AtomMemModel, failure_memmodel: AtomMemModel): bool {.enforcenoraises.} =
|
||||
weak: bool, success_memmodel: AtomMemModel, failure_memmodel: AtomMemModel): bool =
|
||||
when sizeof(T) == 8:
|
||||
interlockedCompareExchange64(p, cast[int64](desired), cast[int64](expected[])) ==
|
||||
cast[int64](expected[])
|
||||
@@ -236,7 +236,7 @@ elif someVcc:
|
||||
interlockedCompareExchange32(p, cast[int32](desired), cast[int32](expected[])) ==
|
||||
cast[int32](expected[])
|
||||
|
||||
proc atomicExchangeN*[T: ptr](p: ptr T, val: T, mem: AtomMemModel): T {.enforcenoraises.} =
|
||||
proc atomicExchangeN*[T: ptr](p: ptr T, val: T, mem: AtomMemModel): T =
|
||||
when sizeof(T) == 8:
|
||||
cast[T](interlockedExchange64(p, cast[int64](val)))
|
||||
elif sizeof(T) == 4:
|
||||
|
||||
@@ -68,7 +68,7 @@ type
|
||||
|
||||
proc `=copy`*(x: var Task, y: Task) {.error.}
|
||||
|
||||
const arcLike = defined(gcArc) or defined(gcAtomicArc) or defined(gcOrc) or defined(gcYrc)
|
||||
const arcLike = defined(gcArc) or defined(gcAtomicArc) or defined(gcOrc)
|
||||
when defined(nimAllowNonVarDestructor) and arcLike:
|
||||
proc `=destroy`*(t: Task) {.inline, gcsafe.} =
|
||||
## Frees the resources allocated for a `Task`.
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
|
||||
##[
|
||||
Thread support for Nim. Threads allow multiple functions to execute concurrently.
|
||||
|
||||
|
||||
In Nim, threads are a low-level construct and using a library like `malebolgia`, `taskpools` or `weave` is recommended.
|
||||
|
||||
|
||||
When creating a thread, you can pass arguments to it. As Nim's garbage collector does not use atomic references, sharing
|
||||
`ref` and other variables managed by the garbage collector between threads is not supported.
|
||||
Use global variables to do so, or pointers.
|
||||
|
||||
|
||||
Memory allocated using [`sharedAlloc`](./system.html#allocShared.t%2CNatural) can be used and shared between threads.
|
||||
|
||||
To communicate between threads, consider using [channels](./system.html#Channel)
|
||||
@@ -44,7 +44,7 @@ joinThreads(thr)
|
||||
|
||||
deinitLock(L)
|
||||
```
|
||||
|
||||
|
||||
When using a memory management strategy that supports shared heaps like `arc` or `boehm`,
|
||||
you can pass pointer to threads and share memory between them, but the memory must outlive the thread.
|
||||
The default memory management strategy, `orc`, supports this.
|
||||
@@ -52,14 +52,14 @@ The example below is **not valid** for memory management strategies that use loc
|
||||
|
||||
```Nim
|
||||
import locks
|
||||
|
||||
|
||||
var l: Lock
|
||||
|
||||
|
||||
proc threadFunc(obj: ptr seq[int]) {.thread.} =
|
||||
withLock l:
|
||||
for i in 0..<100:
|
||||
obj[].add(obj[].len * obj[].len)
|
||||
|
||||
|
||||
proc threadHandler() =
|
||||
var thr: array[0..4, Thread[ptr seq[int]]]
|
||||
var s = newSeq[int]()
|
||||
@@ -68,7 +68,7 @@ proc threadHandler() =
|
||||
createThread(thr[i], threadFunc, s.addr)
|
||||
joinThreads(thr)
|
||||
echo s
|
||||
|
||||
|
||||
initLock(l)
|
||||
threadHandler()
|
||||
deinitLock(l)
|
||||
@@ -303,5 +303,5 @@ else:
|
||||
proc createThread*(t: var Thread[void], tp: proc () {.thread, nimcall.}) =
|
||||
createThread[void](t, tp)
|
||||
|
||||
when not defined(gcOrc) and not defined(gcYrc):
|
||||
when not defined(gcOrc):
|
||||
include system/threadids
|
||||
|
||||
@@ -25,7 +25,7 @@ when not (defined(cpu16) or defined(cpu8)):
|
||||
bytes: int
|
||||
data: WideCString
|
||||
|
||||
const arcLike = defined(gcArc) or defined(gcAtomicArc) or defined(gcOrc) or defined(gcYrc)
|
||||
const arcLike = defined(gcArc) or defined(gcAtomicArc) or defined(gcOrc)
|
||||
when defined(nimAllowNonVarDestructor) and arcLike:
|
||||
proc `=destroy`(a: WideCStringObj) =
|
||||
if a.data != nil:
|
||||
|
||||
@@ -125,7 +125,7 @@ proc unsafeAddr*[T](x: T): ptr T {.magic: "Addr", noSideEffect.} =
|
||||
|
||||
const ThisIsSystem = true
|
||||
|
||||
const arcLikeMem = defined(gcArc) or defined(gcAtomicArc) or defined(gcOrc) or defined(gcYrc)
|
||||
const arcLikeMem = defined(gcArc) or defined(gcAtomicArc) or defined(gcOrc)
|
||||
|
||||
when defined(nimAllowNonVarDestructor) and arcLikeMem:
|
||||
proc new*[T](a: var ref T, finalizer: proc (x: T) {.nimcall.}) {.
|
||||
@@ -356,7 +356,7 @@ proc low*(x: string): int {.magic: "Low", noSideEffect.}
|
||||
## See also:
|
||||
## * `high(string) <#high,string>`_
|
||||
|
||||
when not defined(gcArc) and not defined(gcOrc) and not defined(gcYrc) and not defined(gcAtomicArc):
|
||||
when not defined(gcArc) and not defined(gcOrc) and not defined(gcAtomicArc):
|
||||
proc shallowCopy*[T](x: var T, y: T) {.noSideEffect, magic: "ShallowCopy".}
|
||||
## Use this instead of `=` for a `shallow copy`:idx:.
|
||||
##
|
||||
@@ -407,7 +407,7 @@ when defined(nimHasDup):
|
||||
|
||||
proc `=sink`*[T](x: var T; y: T) {.inline, nodestroy, magic: "Asgn".} =
|
||||
## Generic `sink`:idx: implementation that can be overridden.
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcYrc) or defined(gcAtomicArc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
x = y
|
||||
else:
|
||||
shallowCopy(x, y)
|
||||
@@ -627,7 +627,7 @@ proc newSeq*[T](s: var seq[T], len: Natural) {.magic: "NewSeq", noSideEffect.}
|
||||
## #inputStrings[3] = "out of bounds"
|
||||
## ```
|
||||
|
||||
proc newSeq*[T](len = 0.Natural): seq[T] {.noSideEffect.} =
|
||||
proc newSeq*[T](len = 0.Natural): seq[T] =
|
||||
## Creates a new sequence of type `seq[T]` with length `len`.
|
||||
##
|
||||
## Note that the sequence will be filled with zeroed entries.
|
||||
@@ -1147,7 +1147,7 @@ template sysAssert(cond: bool, msg: string) =
|
||||
const hasAlloc = (hostOS != "standalone" or not defined(nogc)) and not defined(nimscript)
|
||||
|
||||
when notJSnotNims and hasAlloc and not defined(nimSeqsV2):
|
||||
proc addChar(s: NimString, c: char): NimString {.compilerproc, gcsafe.}
|
||||
proc addChar(s: NimString, c: char): NimString {.compilerproc, benign.}
|
||||
|
||||
when defined(nimscript) or not defined(nimSeqsV2):
|
||||
proc add*[T](x: var seq[T], y: sink T) {.magic: "AppendSeqElem", noSideEffect.}
|
||||
@@ -1459,7 +1459,6 @@ proc isNil*[T: proc | iterator {.closure.}](x: T): bool {.noSideEffect, magic: "
|
||||
## `== nil`.
|
||||
|
||||
proc supportsCopyMem(t: typedesc): bool {.magic: "TypeTrait".}
|
||||
proc canFormCycles(t: typedesc): bool {.magic: "TypeTrait".}
|
||||
|
||||
when defined(nimHasTopDownInference):
|
||||
# magic used for seq type inference
|
||||
@@ -1665,7 +1664,7 @@ when not defined(js) and hasThreadSupport and hostOS != "standalone":
|
||||
|
||||
when not defined(js) and defined(nimV2):
|
||||
type
|
||||
DestructorProc = proc (p: pointer) {.nimcall, gcsafe, raises: [].}
|
||||
DestructorProc = proc (p: pointer) {.nimcall, benign, raises: [].}
|
||||
TNimTypeV2 {.compilerproc.} = object
|
||||
destructor: pointer
|
||||
size: int
|
||||
@@ -1777,7 +1776,7 @@ when not defined(nimscript):
|
||||
when not declared(sysFatal):
|
||||
include "system/fatal"
|
||||
|
||||
proc echo*(x: varargs[typed, `$`]) {.magic: "Echo", gcsafe, sideEffect.}
|
||||
proc echo*(x: varargs[typed, `$`]) {.magic: "Echo", benign, sideEffect.}
|
||||
## Writes and flushes the parameters to the standard output.
|
||||
##
|
||||
## Special built-in that takes a variable number of arguments. Each argument
|
||||
@@ -1884,7 +1883,7 @@ when notJSnotNims:
|
||||
## lead to the `raise` statement. This only works for debug builds.
|
||||
|
||||
var
|
||||
globalRaiseHook*: proc (e: ref Exception): bool {.nimcall, gcsafe.}
|
||||
globalRaiseHook*: proc (e: ref Exception): bool {.nimcall, benign.}
|
||||
## With this hook you can influence exception handling on a global level.
|
||||
## If not nil, every 'raise' statement ends up calling this hook.
|
||||
##
|
||||
@@ -1893,7 +1892,7 @@ when notJSnotNims:
|
||||
## If `globalRaiseHook` returns false, the exception is caught and does
|
||||
## not propagate further through the call stack.
|
||||
|
||||
localRaiseHook* {.threadvar.}: proc (e: ref Exception): bool {.nimcall, gcsafe.}
|
||||
localRaiseHook* {.threadvar.}: proc (e: ref Exception): bool {.nimcall, benign.}
|
||||
## With this hook you can influence exception handling on a
|
||||
## thread local level.
|
||||
## If not nil, every 'raise' statement ends up calling this hook.
|
||||
@@ -1903,7 +1902,7 @@ when notJSnotNims:
|
||||
## If `localRaiseHook` returns false, the exception
|
||||
## is caught and does not propagate further through the call stack.
|
||||
|
||||
outOfMemHook*: proc () {.nimcall, tags: [], gcsafe, raises: [].}
|
||||
outOfMemHook*: proc () {.nimcall, tags: [], benign, raises: [].}
|
||||
## Set this variable to provide a procedure that should be called
|
||||
## in case of an `out of memory`:idx: event. The standard handler
|
||||
## writes an error message and terminates the program.
|
||||
@@ -1924,7 +1923,7 @@ when notJSnotNims:
|
||||
## If the handler does not raise an exception, ordinary control flow
|
||||
## continues and the program is terminated.
|
||||
|
||||
unhandledExceptionHook*: proc (e: ref Exception) {.nimcall, tags: [], gcsafe, raises: [].}
|
||||
unhandledExceptionHook*: proc (e: ref Exception) {.nimcall, tags: [], benign, raises: [].}
|
||||
## Set this variable to provide a procedure that should be called
|
||||
## in case of an `unhandle exception` event. The standard handler
|
||||
## writes an error message and terminates the program, except when
|
||||
@@ -2067,7 +2066,7 @@ when hostOS == "standalone" and defined(nogc):
|
||||
if s == nil or s.len == 0: result = cstring""
|
||||
else: result = cast[cstring](addr s.data)
|
||||
|
||||
proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo", gcsafe.}
|
||||
proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo", benign.}
|
||||
## Get type information for `x`.
|
||||
##
|
||||
## Ordinary code should not use this, but the `typeinfo module
|
||||
@@ -2286,21 +2285,21 @@ when not defined(js) and declared(alloc0) and declared(dealloc):
|
||||
dealloc(a)
|
||||
|
||||
when notJSnotNims and hostOS != "standalone":
|
||||
proc getCurrentException*(): ref Exception {.compilerRtl, inl, gcsafe.} =
|
||||
proc getCurrentException*(): ref Exception {.compilerRtl, inl, benign.} =
|
||||
## Retrieves the current exception; if there is none, `nil` is returned.
|
||||
result = currException
|
||||
|
||||
proc nimBorrowCurrentException(): ref Exception {.compilerRtl, inl, gcsafe, nodestroy.} =
|
||||
proc nimBorrowCurrentException(): ref Exception {.compilerRtl, inl, benign, nodestroy.} =
|
||||
# .nodestroy here so that we do not produce a write barrier as the
|
||||
# C codegen only uses it in a borrowed way:
|
||||
result = currException
|
||||
|
||||
proc getCurrentExceptionMsg*(): string {.inline, gcsafe.} =
|
||||
proc getCurrentExceptionMsg*(): string {.inline, benign.} =
|
||||
## Retrieves the error message that was attached to the current
|
||||
## exception; if there is none, `""` is returned.
|
||||
return if currException == nil: "" else: currException.msg
|
||||
|
||||
proc setCurrentException*(exc: ref Exception) {.inline, gcsafe.} =
|
||||
proc setCurrentException*(exc: ref Exception) {.inline, benign.} =
|
||||
## Sets the current exception.
|
||||
##
|
||||
## .. warning:: Only use this if you know what you are doing.
|
||||
@@ -2562,7 +2561,7 @@ when compileOption("rangechecks"):
|
||||
else:
|
||||
template rangeCheck*(cond) = discard
|
||||
|
||||
when not defined(gcArc) and not defined(gcOrc) and not defined(gcYrc) and not defined(gcAtomicArc):
|
||||
when not defined(gcArc) and not defined(gcOrc) and not defined(gcAtomicArc):
|
||||
proc shallow*[T](s: var seq[T]) {.noSideEffect, inline.} =
|
||||
## Marks a sequence `s` as `shallow`:idx:. Subsequent assignments will not
|
||||
## perform deep copies of `s`.
|
||||
@@ -2631,7 +2630,7 @@ when hasAlloc or defined(nimscript):
|
||||
setLen(x, xl+item.len)
|
||||
var j = xl-1
|
||||
while j >= i:
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcYrc) or defined(gcAtomicArc):
|
||||
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
||||
x[j+item.len] = move x[j]
|
||||
else:
|
||||
shallowCopy(x[j+item.len], x[j])
|
||||
|
||||
@@ -104,8 +104,6 @@ type
|
||||
zeroField: int # 0 means cell is not used (overlaid with typ field)
|
||||
# 1 means cell is manually managed pointer
|
||||
# otherwise a PNimType is stored in there
|
||||
when sizeof(int) == 4: # 32-bit only
|
||||
headerAlignPad: array[8, byte] # so addr(data) ≡ 8 (mod 16)
|
||||
else:
|
||||
alignment: int
|
||||
|
||||
@@ -479,8 +477,7 @@ iterator allObjects(m: var MemRegion): pointer {.inline.} =
|
||||
a = a +% size
|
||||
else:
|
||||
let c = cast[PBigChunk](c)
|
||||
# prev stores the aligned data pointer set during rawAlloc
|
||||
yield cast[pointer](c.prev)
|
||||
yield addr(c.data)
|
||||
m.locked = false
|
||||
|
||||
proc iterToProc*(iter: typed, envType: typedesc; procName: untyped) {.
|
||||
@@ -727,7 +724,7 @@ proc getSmallChunk(a: var MemRegion): PSmallChunk =
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
when not defined(gcDestructors):
|
||||
proc isAllocatedPtr(a: MemRegion, p: pointer): bool {.gcsafe.}
|
||||
proc isAllocatedPtr(a: MemRegion, p: pointer): bool {.benign.}
|
||||
|
||||
when true:
|
||||
template allocInv(a: MemRegion): bool = true
|
||||
@@ -780,10 +777,7 @@ proc deallocBigChunk(a: var MemRegion, c: PBigChunk) =
|
||||
sysAssert a.occ >= 0, "rawDealloc: negative occupied memory (case B)"
|
||||
when not defined(gcDestructors):
|
||||
a.deleted = getBottom(a)
|
||||
# prev stores the aligned data pointer that was added to the AVL tree during allocation
|
||||
del(a, a.root, cast[int](c.prev))
|
||||
# Reset prev before freeing (required by listAdd assertions in freeBigChunk)
|
||||
c.prev = nil
|
||||
del(a, a.root, cast[int](addr(c.data)))
|
||||
if c.size >= HugeChunkSize: freeHugeChunk(a, c)
|
||||
else: freeBigChunk(a, c)
|
||||
|
||||
@@ -851,14 +845,7 @@ when defined(heaptrack):
|
||||
proc heaptrack_malloc(a: pointer, size: int) {.cdecl, importc, dynlib: heaptrackLib.}
|
||||
proc heaptrack_free(a: pointer) {.cdecl, importc, dynlib: heaptrackLib.}
|
||||
|
||||
proc bigChunkAlignOffset(alignment: int): int {.inline.} =
|
||||
## Compute the alignment offset for big chunk data.
|
||||
if alignment == 0:
|
||||
result = 0
|
||||
else:
|
||||
result = align(sizeof(BigChunk) + sizeof(FreeCell), alignment) - sizeof(BigChunk) - sizeof(FreeCell)
|
||||
|
||||
proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer =
|
||||
proc rawAlloc(a: var MemRegion, requestedSize: int): pointer =
|
||||
when defined(nimTypeNames):
|
||||
inc(a.allocCounter)
|
||||
sysAssert(allocInv(a), "rawAlloc: begin")
|
||||
@@ -868,9 +855,7 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer
|
||||
sysAssert(size >= requestedSize, "insufficient allocated size!")
|
||||
#c_fprintf(stdout, "alloc; size: %ld; %ld\n", requestedSize, size)
|
||||
|
||||
# For custom alignments > MemAlign, force big chunk allocation
|
||||
# Small chunks cannot handle arbitrary alignments due to fixed cell boundaries
|
||||
if size <= SmallChunkSize-smallChunkOverhead() and alignment == 0:
|
||||
if size <= SmallChunkSize-smallChunkOverhead():
|
||||
template fetchSharedCells(tc: PSmallChunk) =
|
||||
# Consumes cells from (potentially) foreign threads from `a.sharedFreeLists[s]`
|
||||
when defined(gcDestructors):
|
||||
@@ -965,21 +950,13 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer
|
||||
if deferredFrees != nil:
|
||||
freeDeferredObjects(a, deferredFrees)
|
||||
|
||||
# For big chunks with custom alignment, allocate extra space.
|
||||
# Since chunks are page-aligned, the needed padding is a compile-time
|
||||
# deterministic value rather than a worst-case estimate.
|
||||
let alignPad = bigChunkAlignOffset(alignment)
|
||||
size = requestedSize + bigChunkOverhead() + alignPad
|
||||
size = requestedSize + bigChunkOverhead() # roundup(requestedSize+bigChunkOverhead(), PageSize)
|
||||
# allocate a large block
|
||||
var c = if size >= HugeChunkSize: getHugeChunk(a, size)
|
||||
else: getBigChunk(a, size)
|
||||
sysAssert c.prev == nil, "rawAlloc 10"
|
||||
sysAssert c.next == nil, "rawAlloc 11"
|
||||
result = addr(c.data) +! alignPad
|
||||
# Store the aligned data pointer in prev for deallocation and GC traversal.
|
||||
# prev is unused while the chunk is allocated (next/prev are free-list links).
|
||||
c.prev = cast[PBigChunk](result)
|
||||
|
||||
result = addr(c.data)
|
||||
sysAssert((cast[int](c) and (MemAlign-1)) == 0, "rawAlloc 13")
|
||||
sysAssert((cast[int](c) and PageMask) == 0, "rawAlloc: Not aligned on a page boundary")
|
||||
when not defined(gcDestructors):
|
||||
@@ -1048,29 +1025,13 @@ proc rawDealloc(a: var MemRegion, p: pointer) =
|
||||
inc(c.free, s)
|
||||
else:
|
||||
inc(c.free, s)
|
||||
# FIX: Don't free small chunks to avoid race condition with sharedFreeLists.
|
||||
#
|
||||
# RACE CONDITION: Between checking foreignCells==0 and calling freeBigChunk,
|
||||
# another thread may read chunk.owner and decide to add a cell to our
|
||||
# sharedFreeLists. If we free the chunk, that cell becomes orphaned.
|
||||
#
|
||||
# SOLUTION: Never free small chunks. They remain in freeSmallChunks[s] and
|
||||
# are reused on next allocation. This maintains the invariant that chunks
|
||||
# in freeSmallChunks[s] have c.free >= s (completely free chunks satisfy this).
|
||||
# If a chunk becomes exhausted (c.free < s), it's removed by line 949.
|
||||
#
|
||||
# TRADEOFF: Memory not returned to OS. Bounded by peak concurrent allocation
|
||||
# per size class (~4KB per active size class per thread, typically <1MB total).
|
||||
#
|
||||
# VERIFIED: TLA+ formal proof shows no race - see VERIFICATION_RESULTS.md
|
||||
#
|
||||
# Original code (REMOVED to fix race):
|
||||
sysAssert(c.free >= s, "Invariant violated: chunk in freeSmallChunks has insufficient space")
|
||||
when false:
|
||||
if c.free == SmallChunkSize-smallChunkOverhead() and c.foreignCells == 0:
|
||||
listRemove(a.freeSmallChunks[s div MemAlign], c)
|
||||
c.size = SmallChunkSize
|
||||
freeBigChunk(a, cast[PBigChunk](c))
|
||||
# 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))
|
||||
|
||||
@@ -1106,9 +1067,7 @@ when not defined(gcDestructors):
|
||||
(cast[ptr FreeCell](p).zeroField >% 1)
|
||||
else:
|
||||
var c = cast[PBigChunk](c)
|
||||
# prev stores the aligned data pointer set during rawAlloc
|
||||
let cellPtr = cast[pointer](c.prev)
|
||||
result = p == cellPtr and cast[ptr FreeCell](p).zeroField >% 1
|
||||
result = p == addr(c.data) and cast[ptr FreeCell](p).zeroField >% 1
|
||||
|
||||
proc prepareForInteriorPointerChecking(a: var MemRegion) {.inline.} =
|
||||
a.minLargeObj = lowGauge(a.root)
|
||||
@@ -1132,8 +1091,7 @@ when not defined(gcDestructors):
|
||||
sysAssert isAllocatedPtr(a, result), " result wrong pointer!"
|
||||
else:
|
||||
var c = cast[PBigChunk](c)
|
||||
# prev stores the aligned data pointer set during rawAlloc
|
||||
var d = cast[pointer](c.prev)
|
||||
var d = addr(c.data)
|
||||
if p >= d and cast[ptr FreeCell](d).zeroField >% 1:
|
||||
result = d
|
||||
sysAssert isAllocatedPtr(a, result), " result wrong pointer!"
|
||||
@@ -1146,8 +1104,7 @@ when not defined(gcDestructors):
|
||||
if avlNode != nil:
|
||||
var k = cast[pointer](avlNode.key)
|
||||
var c = cast[PBigChunk](pageAddr(k))
|
||||
# prev stores the aligned data pointer (the AVL tree key)
|
||||
sysAssert(cast[pointer](c.prev) == k, " k is not the aligned address!")
|
||||
sysAssert(addr(c.data) == k, " k is not the same as addr(c.data)!")
|
||||
if cast[ptr FreeCell](k).zeroField >% 1:
|
||||
result = k
|
||||
sysAssert isAllocatedPtr(a, result), " result wrong pointer!"
|
||||
@@ -1366,4 +1323,4 @@ template instantiateForRegion(allocator: untyped) {.dirty.} =
|
||||
#sharedMemStatsShared(sharedHeap.currMem - sharedHeap.freeMem)
|
||||
{.pop.}
|
||||
|
||||
{.pop.}
|
||||
{.pop.}
|
||||
|
||||
@@ -16,7 +16,7 @@ runtime type and only contains a reference count.
|
||||
|
||||
{.push raises: [], rangeChecks: off.}
|
||||
|
||||
when defined(gcOrc) or defined(gcYrc):
|
||||
when defined(gcOrc):
|
||||
const
|
||||
rcIncrement = 0b10000 # so that lowest 4 bits are not touched
|
||||
rcMask = 0b1111
|
||||
@@ -36,12 +36,12 @@ type
|
||||
rc: int # the object header is now a single RC field.
|
||||
# we could remove it in non-debug builds for the 'owned ref'
|
||||
# design but this seems unwise.
|
||||
when defined(gcOrc) or defined(gcYrc):
|
||||
when defined(gcOrc):
|
||||
rootIdx: int # thanks to this we can delete potential cycle roots
|
||||
# in O(1) without doubly linked lists
|
||||
when defined(nimArcDebug) or defined(nimArcIds):
|
||||
refId: int
|
||||
when (defined(gcOrc) or defined(gcYrc)) and orcLeakDetector:
|
||||
when defined(gcOrc) and orcLeakDetector:
|
||||
filename: cstring
|
||||
line: int
|
||||
|
||||
@@ -74,7 +74,7 @@ elif defined(nimArcIds):
|
||||
|
||||
const traceId = -1
|
||||
|
||||
when (defined(gcAtomicArc) or defined(gcYrc)) and hasThreadSupport:
|
||||
when defined(gcAtomicArc) and hasThreadSupport:
|
||||
template decrement(cell: Cell): untyped =
|
||||
discard atomicDec(cell.rc, rcIncrement)
|
||||
template increment(cell: Cell): untyped =
|
||||
@@ -119,7 +119,7 @@ proc nimNewObjUninit(size, alignment: int): pointer {.compilerRtl.} =
|
||||
else:
|
||||
result = cast[ptr RefHeader](alignedAlloc(s, alignment) +! hdrSize)
|
||||
head(result).rc = 0
|
||||
when defined(gcOrc) or defined(gcYrc):
|
||||
when defined(gcOrc):
|
||||
head(result).rootIdx = 0
|
||||
when defined(nimArcDebug):
|
||||
head(result).refId = gRefId
|
||||
@@ -157,7 +157,7 @@ proc nimIncRef(p: pointer) {.compilerRtl, inl.} =
|
||||
when traceCollector:
|
||||
cprintf("[INCREF] %p\n", head(p))
|
||||
|
||||
when not (defined(gcOrc) or defined(gcYrc)) or defined(nimThinout):
|
||||
when not defined(gcOrc) or defined(nimThinout):
|
||||
proc unsureAsgnRef(dest: ptr pointer, src: pointer) {.inline.} =
|
||||
# This is only used by the old RTTI mechanism and we know
|
||||
# that 'dest[]' is nil and needs no destruction. Which is really handy
|
||||
@@ -208,9 +208,7 @@ proc nimDestroyAndDispose(p: pointer) {.compilerRtl, quirky, raises: [].} =
|
||||
cstderr.rawWrite "has destructor!\n"
|
||||
nimRawDispose(p, rti.align)
|
||||
|
||||
when defined(gcYrc):
|
||||
include yrc
|
||||
elif defined(gcOrc):
|
||||
when defined(gcOrc):
|
||||
when defined(nimThinout):
|
||||
include cyclebreaker
|
||||
else:
|
||||
@@ -227,7 +225,7 @@ proc nimDecRefIsLast(p: pointer): bool {.compilerRtl, inl.} =
|
||||
writeStackTrace()
|
||||
cfprintf(cstderr, "[DecRef] %p %ld\n", p, cell.count)
|
||||
|
||||
when (defined(gcAtomicArc) or defined(gcYrc)) and hasThreadSupport:
|
||||
when defined(gcAtomicArc) and hasThreadSupport:
|
||||
# `atomicDec` returns the new value
|
||||
if atomicDec(cell.rc, rcIncrement) == -rcIncrement:
|
||||
result = true
|
||||
@@ -253,7 +251,7 @@ proc GC_ref*[T](x: ref T) =
|
||||
## New runtime only supports this operation for 'ref T'.
|
||||
if x != nil: nimIncRef(cast[pointer](x))
|
||||
|
||||
when not (defined(gcOrc) or defined(gcYrc)):
|
||||
when not defined(gcOrc):
|
||||
template GC_fullCollect* =
|
||||
## Forces a full garbage collection pass. With `--mm:arc` a nop.
|
||||
discard
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
|
||||
include seqs_v2_reimpl
|
||||
|
||||
proc genericResetAux(dest: pointer, n: ptr TNimNode) {.gcsafe.}
|
||||
proc genericResetAux(dest: pointer, n: ptr TNimNode) {.benign.}
|
||||
|
||||
proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) {.gcsafe.}
|
||||
proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) {.benign.}
|
||||
proc genericAssignAux(dest, src: pointer, n: ptr TNimNode,
|
||||
shallow: bool) {.gcsafe.} =
|
||||
shallow: bool) {.benign.} =
|
||||
var
|
||||
d = cast[int](dest)
|
||||
s = cast[int](src)
|
||||
@@ -187,8 +187,8 @@ proc genericAssignOpenArray(dest, src: pointer, len: int,
|
||||
genericAssign(cast[pointer](d +% i *% mt.base.size),
|
||||
cast[pointer](s +% i *% mt.base.size), mt.base)
|
||||
|
||||
proc objectInit(dest: pointer, typ: PNimType) {.compilerproc, gcsafe.}
|
||||
proc objectInitAux(dest: pointer, n: ptr TNimNode) {.gcsafe.} =
|
||||
proc objectInit(dest: pointer, typ: PNimType) {.compilerproc, benign.}
|
||||
proc objectInitAux(dest: pointer, n: ptr TNimNode) {.benign.} =
|
||||
var d = cast[int](dest)
|
||||
case n.kind
|
||||
of nkNone: sysAssert(false, "objectInitAux")
|
||||
@@ -224,7 +224,7 @@ proc objectInit(dest: pointer, typ: PNimType) =
|
||||
|
||||
# ---------------------- assign zero -----------------------------------------
|
||||
|
||||
proc genericReset(dest: pointer, mt: PNimType) {.compilerproc, gcsafe.}
|
||||
proc genericReset(dest: pointer, mt: PNimType) {.compilerproc, benign.}
|
||||
proc genericResetAux(dest: pointer, n: ptr TNimNode) =
|
||||
var d = cast[int](dest)
|
||||
case n.kind
|
||||
|
||||
@@ -51,7 +51,7 @@ proc split(t: var PAvlNode) =
|
||||
t.link[0] = temp
|
||||
inc t.level
|
||||
|
||||
proc add(a: var MemRegion, t: var PAvlNode, key, upperBound: int) {.gcsafe.} =
|
||||
proc add(a: var MemRegion, t: var PAvlNode, key, upperBound: int) {.benign.} =
|
||||
if t.isBottom:
|
||||
t = allocAvlNode(a, key, upperBound)
|
||||
else:
|
||||
@@ -70,7 +70,7 @@ proc add(a: var MemRegion, t: var PAvlNode, key, upperBound: int) {.gcsafe.} =
|
||||
skew(t)
|
||||
split(t)
|
||||
|
||||
proc del(a: var MemRegion, t: var PAvlNode, x: int) {.gcsafe.} =
|
||||
proc del(a: var MemRegion, t: var PAvlNode, x: int) {.benign.} =
|
||||
if isBottom(t): return
|
||||
a.last = t
|
||||
if x <% t.key:
|
||||
|
||||
@@ -42,12 +42,13 @@ Complete traversal is done in this way::
|
||||
|
||||
]#
|
||||
|
||||
when defined(gcOrc) or defined(gcArc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcOrc) or defined(gcArc) or defined(gcAtomicArc):
|
||||
type
|
||||
PCell = Cell
|
||||
|
||||
when not declaredInScope(PageShift):
|
||||
include bitmasks
|
||||
|
||||
else:
|
||||
type
|
||||
RefCount = int
|
||||
@@ -55,14 +56,11 @@ else:
|
||||
Cell {.pure.} = object
|
||||
refcount: RefCount # the refcount and some flags
|
||||
typ: PNimType
|
||||
|
||||
when trackAllocationSource:
|
||||
filename: cstring
|
||||
line: int
|
||||
when useCellIds:
|
||||
id: int
|
||||
when (not trackAllocationSource) and (not useCellIds) and sizeof(int) == 4: # 32-bit only
|
||||
headerAlignPad: array[8, byte] # so addr(data) ≡ 8 (mod 16)
|
||||
|
||||
PCell = ptr Cell
|
||||
|
||||
@@ -80,7 +78,7 @@ type
|
||||
head: PPageDesc
|
||||
data: PPageDescArray
|
||||
|
||||
when defined(gcOrc) or defined(gcArc) or defined(gcAtomicArc) or defined(gcYrc):
|
||||
when defined(gcOrc) or defined(gcArc) or defined(gcAtomicArc):
|
||||
discard
|
||||
else:
|
||||
include cellseqs_v1
|
||||
|
||||
@@ -181,10 +181,10 @@ proc deinitRawChannel(p: pointer) =
|
||||
|
||||
when not usesDestructors:
|
||||
proc storeAux(dest, src: pointer, mt: PNimType, t: PRawChannel,
|
||||
mode: LoadStoreMode) {.gcsafe.}
|
||||
mode: LoadStoreMode) {.benign.}
|
||||
|
||||
proc storeAux(dest, src: pointer, n: ptr TNimNode, t: PRawChannel,
|
||||
mode: LoadStoreMode) {.gcsafe.} =
|
||||
mode: LoadStoreMode) {.benign.} =
|
||||
var
|
||||
d = cast[int](dest)
|
||||
s = cast[int](src)
|
||||
|
||||
@@ -38,21 +38,6 @@ proc `==`*[T](x, y: ptr T): bool {.magic: "EqRef", noSideEffect.}
|
||||
proc `==`*[T: proc | iterator](x, y: T): bool {.magic: "EqProc", noSideEffect.}
|
||||
## Checks that two `proc` variables refer to the same procedure.
|
||||
|
||||
when true:
|
||||
# guard against string converted to cstring implicitly; see also #bug #25488
|
||||
proc isNil*(x: string): bool {.noSideEffect, error: "'isNil' is invalid for 'string'".}
|
||||
|
||||
|
||||
# bug #9149; ensure that 'typeof(nil)' does not match *too* well by using 'typeof(nil) | typeof(nil)',
|
||||
# especially for converters, see tests/overload/tconverter_to_string.nim
|
||||
# Eventually we will be able to remove this hack completely.
|
||||
|
||||
proc `==`*(x: string; y: typeof(nil) | typeof(nil)): bool {.error: "'nil' is invalid for 'string'".} =
|
||||
discard
|
||||
|
||||
proc `==`*(x: typeof(nil) | typeof(nil); y: string): bool {.error: "'nil' is invalid for 'string'".} =
|
||||
discard
|
||||
|
||||
proc `<=`*[Enum: enum](x, y: Enum): bool {.magic: "LeEnum", noSideEffect.}
|
||||
proc `<=`*(x, y: string): bool {.magic: "LeStr", noSideEffect.} =
|
||||
## Compares two strings and returns true if `x` is lexicographically
|
||||
|
||||
@@ -62,8 +62,8 @@ const
|
||||
colorMask = 0b011
|
||||
|
||||
type
|
||||
TraceProc = proc (p, env: pointer) {.nimcall, gcsafe, raises: [].}
|
||||
DisposeProc = proc (p: pointer) {.nimcall, gcsafe, raises: [].}
|
||||
TraceProc = proc (p, env: pointer) {.nimcall, benign, raises: [].}
|
||||
DisposeProc = proc (p: pointer) {.nimcall, benign, raises: [].}
|
||||
|
||||
template color(c): untyped = c.rc and colorMask
|
||||
template setColor(c, col) =
|
||||
|
||||
@@ -58,9 +58,9 @@ proc put(t: var PtrTable; key, val: pointer) =
|
||||
inc t.counter
|
||||
|
||||
proc genericDeepCopyAux(dest, src: pointer, mt: PNimType;
|
||||
tab: var PtrTable) {.gcsafe.}
|
||||
tab: var PtrTable) {.benign.}
|
||||
proc genericDeepCopyAux(dest, src: pointer, n: ptr TNimNode;
|
||||
tab: var PtrTable) {.gcsafe.} =
|
||||
tab: var PtrTable) {.benign.} =
|
||||
var
|
||||
d = cast[int](dest)
|
||||
s = cast[int](src)
|
||||
|
||||
@@ -16,7 +16,7 @@ import stacktraces
|
||||
const noStacktraceAvailable = "No stack traceback available\n"
|
||||
|
||||
var
|
||||
errorMessageWriter*: (proc(msg: string) {.tags: [WriteIOEffect], gcsafe,
|
||||
errorMessageWriter*: (proc(msg: string) {.tags: [WriteIOEffect], benign,
|
||||
nimcall, raises: [].})
|
||||
## Function that will be called
|
||||
## instead of `stdmsg.write` when printing stacktrace.
|
||||
@@ -61,10 +61,10 @@ proc showErrorMessage2(data: string) {.inline.} =
|
||||
# TODO showErrorMessage will turn it back to a string when a hook is set (!)
|
||||
showErrorMessage(data.cstring, data.len)
|
||||
|
||||
proc chckIndx(i, a, b: int): int {.inline, compilerproc, gcsafe.}
|
||||
proc chckRange(i, a, b: int): int {.inline, compilerproc, gcsafe.}
|
||||
proc chckRangeF(x, a, b: float): float {.inline, compilerproc, gcsafe.}
|
||||
proc chckNil(p: pointer) {.noinline, compilerproc, gcsafe.}
|
||||
proc chckIndx(i, a, b: int): int {.inline, compilerproc, benign.}
|
||||
proc chckRange(i, a, b: int): int {.inline, compilerproc, benign.}
|
||||
proc chckRangeF(x, a, b: float): float {.inline, compilerproc, benign.}
|
||||
proc chckNil(p: pointer) {.noinline, compilerproc, benign.}
|
||||
|
||||
type
|
||||
GcFrame = ptr GcFrameHeader
|
||||
@@ -653,7 +653,7 @@ when defined(cpp) and appType != "lib" and not gotoBasedExceptions and
|
||||
rawQuit 1
|
||||
|
||||
when not defined(noSignalHandler) and not defined(useNimRtl):
|
||||
type Sighandler = proc (a: cint) {.noconv, gcsafe.}
|
||||
type Sighandler = proc (a: cint) {.noconv, benign.}
|
||||
# xxx factor with ansi_c.CSighandlerT, posix.Sighandler
|
||||
|
||||
proc signalHandler(sign: cint) {.exportc: "signalHandler", noconv, raises: [].} =
|
||||
|
||||
@@ -76,7 +76,7 @@ const
|
||||
when withRealTime and not declared(getTicks):
|
||||
include "system/timers"
|
||||
when defined(memProfiler):
|
||||
proc nimProfile(requestedSize: int) {.gcsafe.}
|
||||
proc nimProfile(requestedSize: int) {.benign.}
|
||||
|
||||
when hasThreadSupport:
|
||||
import std/sharedlist
|
||||
@@ -97,7 +97,7 @@ type
|
||||
waZctDecRef, waPush
|
||||
#, waDebug
|
||||
|
||||
Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, gcsafe, raises: [].}
|
||||
Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign, raises: [], gcsafe.}
|
||||
# A ref type can have a finalizer that is called before the object's
|
||||
# storage is freed.
|
||||
|
||||
@@ -222,11 +222,11 @@ template gcTrace(cell, state: untyped) =
|
||||
when traceGC: traceCell(cell, state)
|
||||
|
||||
# forward declarations:
|
||||
proc collectCT(gch: var GcHeap) {.gcsafe, raises: [].}
|
||||
proc isOnStack(p: pointer): bool {.noinline, gcsafe, raises: [].}
|
||||
proc forAllChildren(cell: PCell, op: WalkOp) {.gcsafe, raises: [].}
|
||||
proc doOperation(p: pointer, op: WalkOp) {.gcsafe, raises: [].}
|
||||
proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) {.gcsafe, raises: [].}
|
||||
proc collectCT(gch: var GcHeap) {.benign, raises: [].}
|
||||
proc isOnStack(p: pointer): bool {.noinline, benign, raises: [].}
|
||||
proc forAllChildren(cell: PCell, op: WalkOp) {.benign, raises: [].}
|
||||
proc doOperation(p: pointer, op: WalkOp) {.benign, raises: [].}
|
||||
proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) {.benign, raises: [].}
|
||||
# we need the prototype here for debugging purposes
|
||||
|
||||
proc incRef(c: PCell) {.inline.} =
|
||||
@@ -338,7 +338,7 @@ proc cellsetReset(s: var CellSet) =
|
||||
|
||||
{.push stacktrace:off.}
|
||||
|
||||
proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.gcsafe.} =
|
||||
proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.benign.} =
|
||||
var d = cast[int](dest)
|
||||
case n.kind
|
||||
of nkSlot: forAllChildrenAux(cast[pointer](d +% n.offset), n.typ, op)
|
||||
@@ -458,16 +458,9 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer =
|
||||
sysAssert(allocInv(gch.region), "rawNewObj begin")
|
||||
gcAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1")
|
||||
collectCT(gch)
|
||||
# Use alignment from typ.base if available, otherwise use MemAlign
|
||||
let alignment = if typ.kind == tyRef and typ.base != nil and
|
||||
typ.base.align > 16: typ.base.align else: 0
|
||||
var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell), alignment))
|
||||
var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell)))
|
||||
#gcAssert typ.kind in {tyString, tySequence} or size >= typ.base.size, "size too small"
|
||||
# Check that the user data (after the Cell header) is properly aligned
|
||||
if alignment == 0:
|
||||
gcAssert((cast[int](res) and (MemAlign-1)) == 0, "newObj: 2.1")
|
||||
else:
|
||||
gcAssert((cast[int](cellToUsr(res)) and (alignment-1)) == 0, "newObj: 2.2")
|
||||
gcAssert((cast[int](res) and (MemAlign-1)) == 0, "newObj: 2")
|
||||
# now it is buffered in the ZCT
|
||||
res.typ = typ
|
||||
setFrameInfo(res)
|
||||
@@ -515,16 +508,9 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl, noinline, raise
|
||||
collectCT(gch)
|
||||
sysAssert(allocInv(gch.region), "newObjRC1 after collectCT")
|
||||
|
||||
# Use alignment from typ.base if available, otherwise use MemAlign
|
||||
let alignment = if typ.kind == tyRef and typ.base != nil and
|
||||
typ.base.align > 16: typ.base.align else: 0
|
||||
var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell), alignment))
|
||||
var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell)))
|
||||
sysAssert(allocInv(gch.region), "newObjRC1 after rawAlloc")
|
||||
# Check that the user data (after the Cell header) is properly aligned
|
||||
if alignment == 0:
|
||||
sysAssert((cast[int](res) and (MemAlign-1)) == 0, "newObj: 2.1")
|
||||
else:
|
||||
sysAssert((cast[int](cellToUsr(res)) and (alignment-1)) == 0, "newObj: 2.2")
|
||||
sysAssert((cast[int](res) and (MemAlign-1)) == 0, "newObj: 2")
|
||||
# now it is buffered in the ZCT
|
||||
res.typ = typ
|
||||
setFrameInfo(res)
|
||||
@@ -687,7 +673,7 @@ proc doOperation(p: pointer, op: WalkOp) =
|
||||
proc nimGCvisit(d: pointer, op: int) {.compilerRtl, raises: [].} =
|
||||
doOperation(d, WalkOp(op))
|
||||
|
||||
proc collectZCT(gch: var GcHeap): bool {.gcsafe, raises: [].}
|
||||
proc collectZCT(gch: var GcHeap): bool {.benign, raises: [].}
|
||||
|
||||
proc collectCycles(gch: var GcHeap) {.raises: [].} =
|
||||
when hasThreadSupport:
|
||||
@@ -930,4 +916,4 @@ when not defined(useNimRtl):
|
||||
result.add "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
|
||||
|
||||
{.pop.} # raises: []
|
||||
{.pop.} # profiler: off, stackTrace: off
|
||||
{.pop.} # profiler: off, stackTrace: off
|
||||
|
||||
@@ -457,7 +457,7 @@ proc deallocHeap*(runFinalizers = true; allowGcAfterwards = true) =
|
||||
initGC()
|
||||
|
||||
type
|
||||
GlobalMarkerProc = proc () {.nimcall, gcsafe, raises: [].}
|
||||
GlobalMarkerProc = proc () {.nimcall, benign, raises: [].}
|
||||
var
|
||||
globalMarkersLen {.exportc.}: int
|
||||
globalMarkers {.exportc.}: array[0..3499, GlobalMarkerProc]
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
## collectors etc.
|
||||
|
||||
type
|
||||
GlobalMarkerProc = proc () {.nimcall, gcsafe, raises: [], tags: [].}
|
||||
GlobalMarkerProc = proc () {.nimcall, benign, raises: [], tags: [].}
|
||||
var
|
||||
globalMarkersLen: int
|
||||
globalMarkers: array[0..3499, GlobalMarkerProc]
|
||||
|
||||
@@ -12,7 +12,7 @@ when hasAlloc:
|
||||
gcOptimizeSpace ## optimize for memory footprint
|
||||
|
||||
when hasAlloc and not defined(js) and not usesDestructors:
|
||||
proc GC_disable*() {.rtl, inl, gcsafe, raises: [].}
|
||||
proc GC_disable*() {.rtl, inl, benign, raises: [].}
|
||||
## Disables the GC. If called `n` times, `n` calls to `GC_enable`
|
||||
## are needed to reactivate the GC.
|
||||
##
|
||||
@@ -20,39 +20,39 @@ when hasAlloc and not defined(js) and not usesDestructors:
|
||||
## the mark and sweep phase with
|
||||
## `GC_disableMarkAndSweep <#GC_disableMarkAndSweep>`_.
|
||||
|
||||
proc GC_enable*() {.rtl, inl, gcsafe, raises: [].}
|
||||
proc GC_enable*() {.rtl, inl, benign, raises: [].}
|
||||
## Enables the GC again.
|
||||
|
||||
proc GC_fullCollect*() {.rtl, gcsafe, raises: [].}
|
||||
proc GC_fullCollect*() {.rtl, benign, raises: [].}
|
||||
## Forces a full garbage collection pass.
|
||||
## Ordinary code does not need to call this (and should not).
|
||||
|
||||
proc GC_enableMarkAndSweep*() {.rtl, gcsafe, raises: [].}
|
||||
proc GC_disableMarkAndSweep*() {.rtl, gcsafe, raises: [].}
|
||||
proc GC_enableMarkAndSweep*() {.rtl, benign, raises: [].}
|
||||
proc GC_disableMarkAndSweep*() {.rtl, benign, raises: [].}
|
||||
## The current implementation uses a reference counting garbage collector
|
||||
## with a seldomly run mark and sweep phase to free cycles. The mark and
|
||||
## sweep phase may take a long time and is not needed if the application
|
||||
## does not create cycles. Thus the mark and sweep phase can be deactivated
|
||||
## and activated separately from the rest of the GC.
|
||||
|
||||
proc GC_getStatistics*(): string {.rtl, gcsafe, raises: [].}
|
||||
proc GC_getStatistics*(): string {.rtl, benign, raises: [].}
|
||||
## Returns an informative string about the GC's activity. This may be useful
|
||||
## for tweaking.
|
||||
|
||||
proc GC_ref*[T](x: ref T) {.magic: "GCref", gcsafe, raises: [].}
|
||||
proc GC_ref*[T](x: seq[T]) {.magic: "GCref", gcsafe, raises: [].}
|
||||
proc GC_ref*(x: string) {.magic: "GCref", gcsafe, raises: [].}
|
||||
proc GC_ref*[T](x: ref T) {.magic: "GCref", benign, raises: [].}
|
||||
proc GC_ref*[T](x: seq[T]) {.magic: "GCref", benign, raises: [].}
|
||||
proc GC_ref*(x: string) {.magic: "GCref", benign, raises: [].}
|
||||
## Marks the object `x` as referenced, so that it will not be freed until
|
||||
## it is unmarked via `GC_unref`.
|
||||
## If called n-times for the same object `x`,
|
||||
## n calls to `GC_unref` are needed to unmark `x`.
|
||||
|
||||
proc GC_unref*[T](x: ref T) {.magic: "GCunref", gcsafe, raises: [].}
|
||||
proc GC_unref*[T](x: seq[T]) {.magic: "GCunref", gcsafe, raises: [].}
|
||||
proc GC_unref*(x: string) {.magic: "GCunref", gcsafe, raises: [].}
|
||||
proc GC_unref*[T](x: ref T) {.magic: "GCunref", benign, raises: [].}
|
||||
proc GC_unref*[T](x: seq[T]) {.magic: "GCunref", benign, raises: [].}
|
||||
proc GC_unref*(x: string) {.magic: "GCunref", benign, raises: [].}
|
||||
## See the documentation of `GC_ref <#GC_ref,string>`_.
|
||||
|
||||
proc nimGC_setStackBottom*(theStackBottom: pointer) {.compilerRtl, noinline, gcsafe, raises: [].}
|
||||
proc nimGC_setStackBottom*(theStackBottom: pointer) {.compilerRtl, noinline, benign, raises: [].}
|
||||
## Expands operating GC stack range to `theStackBottom`. Does nothing
|
||||
## if current stack bottom is already lower than `theStackBottom`.
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ type
|
||||
# local
|
||||
waMarkPrecise # fast precise marking
|
||||
|
||||
Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, gcsafe, raises: [].}
|
||||
Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign, raises: [], gcsafe.}
|
||||
# A ref type can have a finalizer that is called before the object's
|
||||
# storage is freed.
|
||||
|
||||
@@ -115,10 +115,10 @@ when BitsPerPage mod (sizeof(int)*8) != 0:
|
||||
{.error: "(BitsPerPage mod BitsPerUnit) should be zero!".}
|
||||
|
||||
# forward declarations:
|
||||
proc collectCT(gch: var GcHeap; size: int) {.gcsafe, raises: [].}
|
||||
proc forAllChildren(cell: PCell, op: WalkOp) {.gcsafe, raises: [].}
|
||||
proc doOperation(p: pointer, op: WalkOp) {.gcsafe, raises: [].}
|
||||
proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) {.gcsafe, raises: [].}
|
||||
proc collectCT(gch: var GcHeap; size: int) {.benign, raises: [].}
|
||||
proc forAllChildren(cell: PCell, op: WalkOp) {.benign, raises: [].}
|
||||
proc doOperation(p: pointer, op: WalkOp) {.benign, raises: [].}
|
||||
proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) {.benign, raises: [].}
|
||||
# we need the prototype here for debugging purposes
|
||||
|
||||
when defined(nimGcRefLeak):
|
||||
@@ -216,7 +216,7 @@ proc initGC() =
|
||||
gch.gcThreadId = atomicInc(gHeapidGenerator) - 1
|
||||
gcAssert(gch.gcThreadId >= 0, "invalid computed thread ID")
|
||||
|
||||
proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.gcsafe.} =
|
||||
proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.benign.} =
|
||||
var d = cast[int](dest)
|
||||
case n.kind
|
||||
of nkSlot: forAllChildrenAux(cast[pointer](d +% n.offset), n.typ, op)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import std/private/syslocks
|
||||
|
||||
when defined(memProfiler):
|
||||
proc nimProfile(requestedSize: int) {.gcsafe.}
|
||||
proc nimProfile(requestedSize: int) {.benign.}
|
||||
|
||||
when defined(useMalloc):
|
||||
proc roundup(x, v: int): int {.inline.} =
|
||||
@@ -41,7 +41,7 @@ else:
|
||||
# We also support 'finalizers'.
|
||||
|
||||
type
|
||||
Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, gcsafe, raises: [].}
|
||||
Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign, raises: [], gcsafe.}
|
||||
# A ref type can have a finalizer that is called before the object's
|
||||
# storage is freed.
|
||||
|
||||
|
||||
@@ -96,8 +96,8 @@ type
|
||||
base*: ptr TNimType
|
||||
node: ptr TNimNode # valid for tyRecord, tyObject, tyTuple, tyEnum
|
||||
finalizer*: pointer # the finalizer for the type
|
||||
marker*: proc (p: pointer, op: int) {.nimcall, gcsafe, tags: [], raises: [].} # marker proc for GC
|
||||
deepcopy: proc (p: pointer): pointer {.nimcall, gcsafe, tags: [], raises: [].}
|
||||
marker*: proc (p: pointer, op: int) {.nimcall, benign, tags: [], raises: [].} # marker proc for GC
|
||||
deepcopy: proc (p: pointer): pointer {.nimcall, benign, tags: [], raises: [].}
|
||||
when defined(nimSeqsV2):
|
||||
typeInfoV2*: pointer
|
||||
when defined(nimTypeNames):
|
||||
|
||||
@@ -51,7 +51,7 @@ proc nimCharToStr(x: char): string {.compilerproc.} =
|
||||
proc isNimException(): bool {.asmNoStackFrame.} =
|
||||
{.emit: "return `lastJSError` && `lastJSError`.m_type;".}
|
||||
|
||||
proc getCurrentException*(): ref Exception {.compilerRtl, gcsafe.} =
|
||||
proc getCurrentException*(): ref Exception {.compilerRtl, benign.} =
|
||||
if isNimException(): result = cast[ref Exception](lastJSError)
|
||||
|
||||
proc getCurrentExceptionMsg*(): string =
|
||||
@@ -72,7 +72,7 @@ proc getCurrentExceptionMsg*(): string =
|
||||
proc setCurrentException*(exc: ref Exception) =
|
||||
lastJSError = cast[PJSError](exc)
|
||||
|
||||
proc closureIterSetExc(e: ref Exception) {.compilerRtl, gcsafe.} =
|
||||
proc closureIterSetExc(e: ref Exception) {.compilerRtl, benign.} =
|
||||
setCurrentException(e)
|
||||
|
||||
proc pushCurrentException(e: sink(ref Exception)) {.compilerRtl, inline.} =
|
||||
|
||||
@@ -6,7 +6,7 @@ when notJSnotNims:
|
||||
## 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,
|
||||
proc copyMem*(dest, source: pointer, size: Natural) {.inline, benign,
|
||||
tags: [], raises: [], enforceNoRaises.}
|
||||
## Copies the contents from the memory at `source` to the memory
|
||||
## at `dest`.
|
||||
@@ -14,7 +14,7 @@ when notJSnotNims:
|
||||
## regions may not overlap. Like any procedure dealing with raw
|
||||
## memory this is **unsafe**.
|
||||
|
||||
proc moveMem*(dest, source: pointer, size: Natural) {.inline, gcsafe,
|
||||
proc moveMem*(dest, source: pointer, size: Natural) {.inline, benign,
|
||||
tags: [], raises: [], enforceNoRaises.}
|
||||
## Copies the contents from the memory at `source` to the memory
|
||||
## at `dest`.
|
||||
@@ -48,17 +48,17 @@ when notJSnotNims:
|
||||
|
||||
when hasAlloc and not defined(js):
|
||||
|
||||
proc allocImpl*(size: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].}
|
||||
proc alloc0Impl*(size: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].}
|
||||
proc deallocImpl*(p: pointer) {.noconv, rtl, tags: [], gcsafe, raises: [].}
|
||||
proc reallocImpl*(p: pointer, newSize: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].}
|
||||
proc realloc0Impl*(p: pointer, oldSize, newSize: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].}
|
||||
proc allocImpl*(size: Natural): pointer {.noconv, rtl, tags: [], benign, raises: [].}
|
||||
proc alloc0Impl*(size: Natural): pointer {.noconv, rtl, tags: [], benign, raises: [].}
|
||||
proc deallocImpl*(p: pointer) {.noconv, rtl, tags: [], benign, raises: [].}
|
||||
proc reallocImpl*(p: pointer, newSize: Natural): pointer {.noconv, rtl, tags: [], benign, raises: [].}
|
||||
proc realloc0Impl*(p: pointer, oldSize, newSize: Natural): pointer {.noconv, rtl, tags: [], benign, raises: [].}
|
||||
|
||||
proc allocSharedImpl*(size: Natural): pointer {.noconv, compilerproc, rtl, gcsafe, raises: [], tags: [].}
|
||||
proc allocShared0Impl*(size: Natural): pointer {.noconv, rtl, gcsafe, raises: [], tags: [].}
|
||||
proc deallocSharedImpl*(p: pointer) {.noconv, rtl, gcsafe, raises: [], tags: [].}
|
||||
proc reallocSharedImpl*(p: pointer, newSize: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].}
|
||||
proc reallocShared0Impl*(p: pointer, oldSize, newSize: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].}
|
||||
proc allocSharedImpl*(size: Natural): pointer {.noconv, compilerproc, rtl, benign, raises: [], tags: [].}
|
||||
proc allocShared0Impl*(size: Natural): pointer {.noconv, rtl, benign, raises: [], tags: [].}
|
||||
proc deallocSharedImpl*(p: pointer) {.noconv, rtl, benign, raises: [], tags: [].}
|
||||
proc reallocSharedImpl*(p: pointer, newSize: Natural): pointer {.noconv, rtl, tags: [], benign, raises: [].}
|
||||
proc reallocShared0Impl*(p: pointer, oldSize, newSize: Natural): pointer {.noconv, rtl, tags: [], benign, raises: [].}
|
||||
|
||||
# Allocator statistics for memory leak tests
|
||||
|
||||
@@ -103,7 +103,7 @@ when hasAlloc and not defined(js):
|
||||
incStat(allocCount)
|
||||
allocImpl(size)
|
||||
|
||||
proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline, gcsafe, raises: [].} =
|
||||
proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline, benign, raises: [].} =
|
||||
## Allocates a new memory block with at least `T.sizeof * size` bytes.
|
||||
##
|
||||
## The block has to be freed with `resize(block, 0) <#resize,ptr.T,Natural>`_
|
||||
@@ -131,7 +131,7 @@ when hasAlloc and not defined(js):
|
||||
incStat(allocCount)
|
||||
alloc0Impl(size)
|
||||
|
||||
proc create*(T: typedesc, size = 1.Positive): ptr T {.inline, gcsafe, raises: [].} =
|
||||
proc create*(T: typedesc, size = 1.Positive): ptr T {.inline, benign, raises: [].} =
|
||||
## Allocates a new memory block with at least `T.sizeof * size` bytes.
|
||||
##
|
||||
## The block has to be freed with `resize(block, 0) <#resize,ptr.T,Natural>`_
|
||||
@@ -174,7 +174,7 @@ when hasAlloc and not defined(js):
|
||||
## from a shared heap.
|
||||
realloc0Impl(p, oldSize, newSize)
|
||||
|
||||
proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline, gcsafe, raises: [].} =
|
||||
proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline, benign, raises: [].} =
|
||||
## Grows or shrinks a given memory block.
|
||||
##
|
||||
## If `p` is **nil** then a new memory block is returned.
|
||||
@@ -187,7 +187,7 @@ when hasAlloc and not defined(js):
|
||||
## from a shared heap.
|
||||
cast[ptr T](realloc(p, T.sizeof * newSize))
|
||||
|
||||
proc dealloc*(p: pointer) {.noconv, compilerproc, rtl, gcsafe, raises: [], tags: [].} =
|
||||
proc dealloc*(p: pointer) {.noconv, compilerproc, rtl, benign, raises: [], tags: [].} =
|
||||
## Frees the memory allocated with `alloc`, `alloc0`,
|
||||
## `realloc`, `create` or `createU`.
|
||||
##
|
||||
@@ -218,7 +218,7 @@ when hasAlloc and not defined(js):
|
||||
allocSharedImpl(size)
|
||||
|
||||
proc createSharedU*(T: typedesc, size = 1.Positive): ptr T {.inline, tags: [],
|
||||
gcsafe, raises: [].} =
|
||||
benign, raises: [].} =
|
||||
## Allocates a new memory block on the shared heap with at
|
||||
## least `T.sizeof * size` bytes.
|
||||
##
|
||||
@@ -296,7 +296,7 @@ when hasAlloc and not defined(js):
|
||||
## `freeShared <#freeShared,ptr.T>`_.
|
||||
cast[ptr T](reallocShared(p, T.sizeof * newSize))
|
||||
|
||||
proc deallocShared*(p: pointer) {.noconv, compilerproc, rtl, gcsafe, raises: [], tags: [].} =
|
||||
proc deallocShared*(p: pointer) {.noconv, compilerproc, rtl, benign, raises: [], tags: [].} =
|
||||
## Frees the memory allocated with `allocShared`, `allocShared0` or
|
||||
## `reallocShared`.
|
||||
##
|
||||
@@ -307,7 +307,7 @@ when hasAlloc and not defined(js):
|
||||
incStat(deallocCount)
|
||||
deallocSharedImpl(p)
|
||||
|
||||
proc freeShared*[T](p: ptr T) {.inline, gcsafe, raises: [].} =
|
||||
proc freeShared*[T](p: ptr T) {.inline, benign, raises: [].} =
|
||||
## Frees the memory allocated with `createShared`, `createSharedU` or
|
||||
## `resizeShared`.
|
||||
##
|
||||
|
||||
@@ -50,7 +50,7 @@ proc deallocSharedImpl(p: pointer) = deallocImpl(p)
|
||||
proc GC_disable() = discard
|
||||
proc GC_enable() = discard
|
||||
|
||||
when not defined(gcOrc) and not defined(gcYrc):
|
||||
when not defined(gcOrc):
|
||||
proc GC_fullCollect() = discard
|
||||
proc GC_enableMarkAndSweep() = discard
|
||||
proc GC_disableMarkAndSweep() = discard
|
||||
|
||||
@@ -29,8 +29,8 @@ const
|
||||
logOrc = defined(nimArcIds)
|
||||
|
||||
type
|
||||
TraceProc = proc (p, env: pointer) {.nimcall, gcsafe, raises: [].}
|
||||
DisposeProc = proc (p: pointer) {.nimcall, gcsafe, raises: [].}
|
||||
TraceProc = proc (p, env: pointer) {.nimcall, benign, raises: [].}
|
||||
DisposeProc = proc (p: pointer) {.nimcall, benign, raises: [].}
|
||||
|
||||
template color(c): untyped = c.rc and colorMask
|
||||
template setColor(c, col) =
|
||||
@@ -433,9 +433,8 @@ proc collectCycles() =
|
||||
rootsThreshold = (if rootsThreshold <= 0: defaultThreshold else: rootsThreshold)
|
||||
rootsThreshold = rootsThreshold div 2 +% rootsThreshold
|
||||
when logOrc:
|
||||
{.cast(raises: []).}:
|
||||
discard cfprintf(cstderr, "[collectCycles] end; freed %ld new threshold %ld touched: %ld mem: %ld rcSum: %ld edges: %ld\n", j.freed, rootsThreshold, j.touched,
|
||||
getOccupiedMem(), j.rcSum, j.edges)
|
||||
cfprintf(cstderr, "[collectCycles] end; freed %ld new threshold %ld touched: %ld mem: %ld rcSum: %ld edges: %ld\n", j.freed, rootsThreshold, j.touched,
|
||||
getOccupiedMem(), j.rcSum, j.edges)
|
||||
when defined(nimOrcStats):
|
||||
inc freedCyclicObjects, j.freed
|
||||
|
||||
@@ -466,13 +465,13 @@ proc GC_runOrc* =
|
||||
|
||||
proc GC_enableOrc*() =
|
||||
## Enables the cycle collector subsystem of `--mm:orc`. This is a `--mm:orc`
|
||||
## specific API. Check with `when defined(gcOrc) or defined(gcYrc)` for its existence.
|
||||
## specific API. Check with `when defined(gcOrc)` for its existence.
|
||||
when not defined(nimStressOrc):
|
||||
rootsThreshold = 0
|
||||
|
||||
proc GC_disableOrc*() =
|
||||
## Disables the cycle collector subsystem of `--mm:orc`. This is a `--mm:orc`
|
||||
## specific API. Check with `when defined(gcOrc) or defined(gcYrc)` for its existence.
|
||||
## specific API. Check with `when defined(gcOrc)` for its existence.
|
||||
when not defined(nimStressOrc):
|
||||
rootsThreshold = high(int)
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@ const doNotUnmap = not (defined(amd64) or defined(i386)) or
|
||||
|
||||
|
||||
when defined(nimAllocPagesViaMalloc):
|
||||
when not defined(gcArc) and not defined(gcOrc) and not defined(gcAtomicArc) and not defined(gcYrc):
|
||||
{.error: "-d:nimAllocPagesViaMalloc is only supported with --mm:arc or --mm:atomicArc or --mm:orc or --mm:yrc".}
|
||||
when not defined(gcArc) and not defined(gcOrc) and not defined(gcAtomicArc):
|
||||
{.error: "-d:nimAllocPagesViaMalloc is only supported with --mm:arc or --mm:atomicArc or --mm:orc".}
|
||||
|
||||
proc osTryAllocPages(size: int): pointer {.inline.} =
|
||||
let base = c_malloc(csize_t size + PageSize - 1 + sizeof(uint32))
|
||||
|
||||
@@ -77,7 +77,7 @@ include system/repr_impl
|
||||
type
|
||||
PByteArray = ptr UncheckedArray[byte] # array[0xffff, byte]
|
||||
|
||||
proc addSetElem(result: var string, elem: int, typ: PNimType) {.gcsafe.} =
|
||||
proc addSetElem(result: var string, elem: int, typ: PNimType) {.benign.} =
|
||||
case typ.kind
|
||||
of tyEnum: add result, reprEnum(elem, typ)
|
||||
of tyBool: add result, reprBool(bool(elem))
|
||||
@@ -147,7 +147,7 @@ when not defined(useNimRtl):
|
||||
for i in 0..cl.indent-1: add result, ' '
|
||||
|
||||
proc reprAux(result: var string, p: pointer, typ: PNimType,
|
||||
cl: var ReprClosure) {.gcsafe.}
|
||||
cl: var ReprClosure) {.benign.}
|
||||
|
||||
proc reprArray(result: var string, p: pointer, typ: PNimType,
|
||||
cl: var ReprClosure) =
|
||||
@@ -188,7 +188,7 @@ when not defined(useNimRtl):
|
||||
add result, "]"
|
||||
|
||||
proc reprRecordAux(result: var string, p: pointer, n: ptr TNimNode,
|
||||
cl: var ReprClosure) {.gcsafe.} =
|
||||
cl: var ReprClosure) {.benign.} =
|
||||
case n.kind
|
||||
of nkNone: sysAssert(false, "reprRecordAux")
|
||||
of nkSlot:
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
#
|
||||
#
|
||||
# Nim's Runtime Library
|
||||
# (c) Copyright 2026 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# Read-write lock (RwLock) for lib/system.
|
||||
# Used by YRC and by traceable containers that perform topology-changing ops.
|
||||
# POSIX: pthread_rwlock_* ; Windows: SRWLOCK (slim reader/writer).
|
||||
|
||||
{.push stackTrace: off.}
|
||||
|
||||
when defined(windows):
|
||||
# SRWLOCK is pointer-sized; use single pointer for ABI compatibility
|
||||
type
|
||||
RwLock* {.importc: "SRWLOCK", header: "<synchapi.h>", final, pure, byref.} = object
|
||||
p: pointer
|
||||
|
||||
proc initializeSRWLock(L: var RwLock) {.importc: "InitializeSRWLock",
|
||||
header: "<synchapi.h>".}
|
||||
proc acquireSRWLockShared(L: var RwLock) {.importc: "AcquireSRWLockShared",
|
||||
header: "<synchapi.h>".}
|
||||
proc releaseSRWLockShared(L: var RwLock) {.importc: "ReleaseSRWLockShared",
|
||||
header: "<synchapi.h>".}
|
||||
proc acquireSRWLockExclusive(L: var RwLock) {.importc: "AcquireSRWLockExclusive",
|
||||
header: "<synchapi.h>".}
|
||||
proc releaseSRWLockExclusive(L: var RwLock) {.importc: "ReleaseSRWLockExclusive",
|
||||
header: "<synchapi.h>".}
|
||||
|
||||
proc initRwLock*(L: var RwLock) {.inline.} =
|
||||
initializeSRWLock(L)
|
||||
proc deinitRwLock*(L: var RwLock) {.inline.} =
|
||||
discard
|
||||
proc acquireRead*(L: var RwLock) {.inline.} =
|
||||
acquireSRWLockShared(L)
|
||||
proc releaseRead*(L: var RwLock) {.inline.} =
|
||||
releaseSRWLockShared(L)
|
||||
proc acquireWrite*(L: var RwLock) {.inline.} =
|
||||
acquireSRWLockExclusive(L)
|
||||
proc releaseWrite*(L: var RwLock) {.inline.} =
|
||||
releaseSRWLockExclusive(L)
|
||||
|
||||
elif defined(genode):
|
||||
{.error: "RwLock is not implemented for Genode".}
|
||||
|
||||
else:
|
||||
# POSIX: pthread_rwlock_*
|
||||
type
|
||||
SysRwLockObj {.importc: "pthread_rwlock_t", pure, final,
|
||||
header: """#include <sys/types.h>
|
||||
#include <pthread.h>""", byref.} = object
|
||||
when defined(linux) and defined(amd64):
|
||||
abi: array[56 div sizeof(clong), clong]
|
||||
|
||||
proc pthread_rwlock_init(rwlock: var SysRwLockObj, attr: pointer): cint {.
|
||||
importc: "pthread_rwlock_init", header: "<pthread.h>", noSideEffect.}
|
||||
proc pthread_rwlock_destroy(rwlock: var SysRwLockObj): cint {.
|
||||
importc: "pthread_rwlock_destroy", header: "<pthread.h>", noSideEffect.}
|
||||
proc pthread_rwlock_rdlock(rwlock: var SysRwLockObj): cint {.
|
||||
importc: "pthread_rwlock_rdlock", header: "<pthread.h>", noSideEffect.}
|
||||
proc pthread_rwlock_wrlock(rwlock: var SysRwLockObj): cint {.
|
||||
importc: "pthread_rwlock_wrlock", header: "<pthread.h>", noSideEffect.}
|
||||
proc pthread_rwlock_unlock(rwlock: var SysRwLockObj): cint {.
|
||||
importc: "pthread_rwlock_unlock", header: "<pthread.h>", noSideEffect.}
|
||||
|
||||
when defined(linux):
|
||||
# PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP: once a writer is waiting,
|
||||
# new readers block. Prevents continuous mutator read-locks from starving
|
||||
# the collector's write-lock acquisition (glibc default is PREFER_READER).
|
||||
type
|
||||
SysRwLockAttr {.importc: "pthread_rwlockattr_t", pure, final,
|
||||
header: "<pthread.h>".} = object
|
||||
const PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP = cint(3)
|
||||
proc pthread_rwlockattr_init(attr: ptr SysRwLockAttr): cint {.
|
||||
importc: "pthread_rwlockattr_init", header: "<pthread.h>".}
|
||||
proc pthread_rwlockattr_destroy(attr: ptr SysRwLockAttr): cint {.
|
||||
importc: "pthread_rwlockattr_destroy", header: "<pthread.h>".}
|
||||
proc pthread_rwlockattr_setkind_np(attr: ptr SysRwLockAttr; pref: cint): cint {.
|
||||
importc: "pthread_rwlockattr_setkind_np", header: "<pthread.h>".}
|
||||
|
||||
when defined(ios):
|
||||
type RwLock* = ptr SysRwLockObj
|
||||
proc initRwLock*(L: var RwLock) =
|
||||
when not declared(c_malloc):
|
||||
proc c_malloc(size: csize_t): pointer {.importc: "malloc", header: "<stdlib.h>".}
|
||||
proc c_free(p: pointer) {.importc: "free", header: "<stdlib.h>".}
|
||||
L = cast[RwLock](c_malloc(csize_t(sizeof(SysRwLockObj))))
|
||||
discard pthread_rwlock_init(L[], nil)
|
||||
proc deinitRwLock*(L: var RwLock) =
|
||||
if L != nil:
|
||||
discard pthread_rwlock_destroy(L[])
|
||||
when not declared(c_free):
|
||||
proc c_free(p: pointer) {.importc: "free", header: "<stdlib.h>".}
|
||||
c_free(L)
|
||||
L = nil
|
||||
proc acquireRead*(L: var RwLock) =
|
||||
discard pthread_rwlock_rdlock(L[])
|
||||
proc releaseRead*(L: var RwLock) =
|
||||
discard pthread_rwlock_unlock(L[])
|
||||
proc acquireWrite*(L: var RwLock) =
|
||||
discard pthread_rwlock_wrlock(L[])
|
||||
proc releaseWrite*(L: var RwLock) =
|
||||
discard pthread_rwlock_unlock(L[])
|
||||
else:
|
||||
type RwLock* = SysRwLockObj
|
||||
proc initRwLock*(L: var RwLock) =
|
||||
when defined(linux):
|
||||
var attr: SysRwLockAttr
|
||||
discard pthread_rwlockattr_init(addr attr)
|
||||
discard pthread_rwlockattr_setkind_np(addr attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP)
|
||||
discard pthread_rwlock_init(L, addr attr)
|
||||
discard pthread_rwlockattr_destroy(addr attr)
|
||||
else:
|
||||
discard pthread_rwlock_init(L, nil)
|
||||
proc deinitRwLock*(L: var RwLock) =
|
||||
discard pthread_rwlock_destroy(L)
|
||||
proc acquireRead*(L: var RwLock) =
|
||||
discard pthread_rwlock_rdlock(L)
|
||||
proc releaseRead*(L: var RwLock) =
|
||||
discard pthread_rwlock_unlock(L)
|
||||
proc acquireWrite*(L: var RwLock) =
|
||||
discard pthread_rwlock_wrlock(L)
|
||||
proc releaseWrite*(L: var RwLock) =
|
||||
discard pthread_rwlock_unlock(L)
|
||||
|
||||
template withReadLock*(L: var RwLock, body: untyped) =
|
||||
acquireRead(L)
|
||||
try:
|
||||
body
|
||||
finally:
|
||||
releaseRead(L)
|
||||
|
||||
template withWriteLock*(L: var RwLock, body: untyped) =
|
||||
acquireWrite(L)
|
||||
try:
|
||||
body
|
||||
finally:
|
||||
releaseWrite(L)
|
||||
|
||||
{.pop.}
|
||||
@@ -11,90 +11,6 @@
|
||||
# import std/typetraits
|
||||
# strs already imported allocateds for us.
|
||||
|
||||
when defined(gcYrc):
|
||||
include rwlocks
|
||||
include threadids
|
||||
|
||||
const
|
||||
NumLockStripes = 64
|
||||
|
||||
type
|
||||
YrcLockState = enum
|
||||
HasNoLock
|
||||
HasMutatorLock
|
||||
HasCollectorLock
|
||||
Collecting
|
||||
|
||||
AlignedRwLock = object
|
||||
## One RwLock per cache line. {.align: 64.} causes the compiler to round
|
||||
## the struct size up to 64 bytes, so consecutive array elements never
|
||||
## share a cache line (sizeof(RwLock) = 56 on Linux x86_64 → 8 byte pad).
|
||||
lock {.align: 64.}: RwLock
|
||||
|
||||
var
|
||||
gYrcLocks: array[NumLockStripes, AlignedRwLock]
|
||||
var
|
||||
lockState {.threadvar.}: YrcLockState
|
||||
|
||||
proc getYrcStripe(): int {.inline.} =
|
||||
## Map this thread to one of the NumLockStripes RwLock stripes.
|
||||
## getThreadId() is already cached thread-locally in threadids.nim.
|
||||
getThreadId() and (NumLockStripes - 1)
|
||||
|
||||
proc acquireMutatorLock() {.compilerRtl, inl.} =
|
||||
if lockState == HasNoLock:
|
||||
acquireRead gYrcLocks[getYrcStripe()].lock
|
||||
lockState = HasMutatorLock
|
||||
|
||||
proc releaseMutatorLock() {.compilerRtl, inl.} =
|
||||
if lockState == HasMutatorLock:
|
||||
lockState = HasNoLock
|
||||
releaseRead gYrcLocks[getYrcStripe()].lock
|
||||
|
||||
template yrcMutatorLock*(t: typedesc; body: untyped) =
|
||||
{.noSideEffect.}:
|
||||
when canFormCycles(t):
|
||||
acquireMutatorLock()
|
||||
try:
|
||||
body
|
||||
finally:
|
||||
{.noSideEffect.}:
|
||||
when canFormCycles(t):
|
||||
releaseMutatorLock()
|
||||
|
||||
template yrcMutatorLockUntyped(body: untyped) =
|
||||
{.noSideEffect.}:
|
||||
acquireMutatorLock()
|
||||
try:
|
||||
body
|
||||
finally:
|
||||
{.noSideEffect.}:
|
||||
releaseMutatorLock()
|
||||
|
||||
template yrcCollectorLock(body: untyped) =
|
||||
if lockState == HasMutatorLock: releaseMutatorLock()
|
||||
let prevState = lockState
|
||||
let hadToAcquire = prevState < HasCollectorLock
|
||||
if hadToAcquire:
|
||||
# Acquire all stripes in ascending order — the only thread ever holding
|
||||
# multiple write locks is the collector, so there is no lock-order cycle.
|
||||
for yrcI in 0..<NumLockStripes:
|
||||
acquireWrite(gYrcLocks[yrcI].lock)
|
||||
lockState = HasCollectorLock
|
||||
try:
|
||||
body
|
||||
finally:
|
||||
if hadToAcquire:
|
||||
for yrcI in 0..<NumLockStripes:
|
||||
releaseWrite(gYrcLocks[yrcI].lock)
|
||||
lockState = prevState
|
||||
|
||||
else:
|
||||
template yrcMutatorLock*(t: typedesc; body: untyped) =
|
||||
body
|
||||
|
||||
template yrcMutatorLockUntyped(body: untyped) =
|
||||
body
|
||||
|
||||
# Some optimizations here may be not to empty-seq-initialize some symbols, then StrictNotNil complains.
|
||||
{.push warning[StrictNotNil]: off.} # See https://github.com/nim-lang/Nim/issues/21401
|
||||
@@ -200,35 +116,33 @@ proc prepareSeqAddUninit(len: int; p: pointer; addlen, elemSize, elemAlign: int)
|
||||
q.cap = newCap
|
||||
result = q
|
||||
|
||||
proc shrink*[T](x: var seq[T]; newLen: Natural) {.tags: [], raises: [], noSideEffect.} =
|
||||
proc shrink*[T](x: var seq[T]; newLen: Natural) {.tags: [], raises: [].} =
|
||||
when nimvm:
|
||||
{.cast(tags: []).}:
|
||||
setLen(x, newLen)
|
||||
else:
|
||||
#sysAssert newLen <= x.len, "invalid newLen parameter for 'shrink'"
|
||||
yrcMutatorLock(T):
|
||||
when not supportsCopyMem(T):
|
||||
for i in countdown(x.len - 1, newLen):
|
||||
reset x[i]
|
||||
# XXX This is wrong for const seqs that were moved into 'x'!
|
||||
{.noSideEffect.}:
|
||||
cast[ptr NimSeqV2[T]](addr x).len = newLen
|
||||
when not supportsCopyMem(T):
|
||||
for i in countdown(x.len - 1, newLen):
|
||||
reset x[i]
|
||||
# XXX This is wrong for const seqs that were moved into 'x'!
|
||||
{.noSideEffect.}:
|
||||
cast[ptr NimSeqV2[T]](addr x).len = newLen
|
||||
|
||||
proc grow*[T](x: var seq[T]; newLen: Natural; value: T) {.nodestroy.} =
|
||||
let oldLen = x.len
|
||||
#sysAssert newLen >= x.len, "invalid newLen parameter for 'grow'"
|
||||
if newLen <= oldLen: return
|
||||
yrcMutatorLock(T):
|
||||
var xu = cast[ptr NimSeqV2[T]](addr x)
|
||||
if xu.p == nil or (xu.p.cap and not strlitFlag) < newLen:
|
||||
xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, newLen - oldLen, sizeof(T), alignof(T)))
|
||||
xu.len = newLen
|
||||
for i in oldLen .. newLen-1:
|
||||
when (NimMajor, NimMinor, NimPatch) >= (2, 3, 1):
|
||||
xu.p.data[i] = `=dup`(value)
|
||||
else:
|
||||
wasMoved(xu.p.data[i])
|
||||
`=copy`(xu.p.data[i], value)
|
||||
var xu = cast[ptr NimSeqV2[T]](addr x)
|
||||
if xu.p == nil or (xu.p.cap and not strlitFlag) < newLen:
|
||||
xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, newLen - oldLen, sizeof(T), alignof(T)))
|
||||
xu.len = newLen
|
||||
for i in oldLen .. newLen-1:
|
||||
when (NimMajor, NimMinor, NimPatch) >= (2, 3, 1):
|
||||
xu.p.data[i] = `=dup`(value)
|
||||
else:
|
||||
wasMoved(xu.p.data[i])
|
||||
`=copy`(xu.p.data[i], value)
|
||||
|
||||
proc add*[T](x: var seq[T]; y: sink T) {.magic: "AppendSeqElem", noSideEffect, nodestroy.} =
|
||||
## Generic proc for adding a data item `y` to a container `x`.
|
||||
@@ -238,32 +152,30 @@ proc add*[T](x: var seq[T]; y: sink T) {.magic: "AppendSeqElem", noSideEffect, n
|
||||
## Generic code becomes much easier to write if the Nim naming scheme is
|
||||
## respected.
|
||||
{.cast(noSideEffect).}:
|
||||
yrcMutatorLock(T):
|
||||
let oldLen = x.len
|
||||
var xu = cast[ptr NimSeqV2[T]](addr x)
|
||||
if xu.p == nil or (xu.p.cap and not strlitFlag) < oldLen+1:
|
||||
xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, 1, sizeof(T), alignof(T)))
|
||||
xu.len = oldLen+1
|
||||
# .nodestroy means `xu.p.data[oldLen] = value` is compiled into a
|
||||
# copyMem(). This is fine as know by construction that
|
||||
# in `xu.p.data[oldLen]` there is nothing to destroy.
|
||||
# We also save the `wasMoved + destroy` pair for the sink parameter.
|
||||
xu.p.data[oldLen] = y
|
||||
let oldLen = x.len
|
||||
var xu = cast[ptr NimSeqV2[T]](addr x)
|
||||
if xu.p == nil or (xu.p.cap and not strlitFlag) < oldLen+1:
|
||||
xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, 1, sizeof(T), alignof(T)))
|
||||
xu.len = oldLen+1
|
||||
# .nodestroy means `xu.p.data[oldLen] = value` is compiled into a
|
||||
# copyMem(). This is fine as know by construction that
|
||||
# in `xu.p.data[oldLen]` there is nothing to destroy.
|
||||
# We also save the `wasMoved + destroy` pair for the sink parameter.
|
||||
xu.p.data[oldLen] = y
|
||||
|
||||
proc setLen[T](s: var seq[T], newlen: Natural) {.nodestroy.} =
|
||||
{.noSideEffect.}:
|
||||
if newlen < s.len:
|
||||
shrink(s, newlen)
|
||||
else:
|
||||
yrcMutatorLock(T):
|
||||
let oldLen = s.len
|
||||
if newlen <= oldLen: return
|
||||
var xu = cast[ptr NimSeqV2[T]](addr s)
|
||||
if xu.p == nil or (xu.p.cap and not strlitFlag) < newlen:
|
||||
xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, newlen - oldLen, sizeof(T), alignof(T)))
|
||||
xu.len = newlen
|
||||
for i in oldLen..<newlen:
|
||||
xu.p.data[i] = default(T)
|
||||
let oldLen = s.len
|
||||
if newlen <= oldLen: return
|
||||
var xu = cast[ptr NimSeqV2[T]](addr s)
|
||||
if xu.p == nil or (xu.p.cap and not strlitFlag) < newlen:
|
||||
xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, newlen - oldLen, sizeof(T), alignof(T)))
|
||||
xu.len = newlen
|
||||
for i in oldLen..<newlen:
|
||||
xu.p.data[i] = default(T)
|
||||
|
||||
proc newSeq[T](s: var seq[T], len: Natural) =
|
||||
shrink(s, 0)
|
||||
@@ -302,12 +214,11 @@ func setLenUninit[T](s: var seq[T], newlen: Natural) {.nodestroy.} =
|
||||
if newlen < s.len:
|
||||
shrink(s, newlen)
|
||||
else:
|
||||
yrcMutatorLock(T):
|
||||
let oldLen = s.len
|
||||
if newlen <= oldLen: return
|
||||
var xu = cast[ptr NimSeqV2[T]](addr s)
|
||||
if xu.p == nil or (xu.p.cap and not strlitFlag) < newlen:
|
||||
xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, newlen - oldLen, sizeof(T), alignof(T)))
|
||||
xu.len = newlen
|
||||
let oldLen = s.len
|
||||
if newlen <= oldLen: return
|
||||
var xu = cast[ptr NimSeqV2[T]](addr s)
|
||||
if xu.p == nil or (xu.p.cap and not strlitFlag) < newlen:
|
||||
xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, newlen - oldLen, sizeof(T), alignof(T)))
|
||||
xu.len = newlen
|
||||
|
||||
{.pop.} # See https://github.com/nim-lang/Nim/issues/21401
|
||||
|
||||
@@ -18,8 +18,7 @@ type
|
||||
|
||||
template frees(s: NimSeqV2Reimpl) =
|
||||
if s.p != nil and (s.p.cap and strlitFlag) != strlitFlag:
|
||||
yrcMutatorLockUntyped:
|
||||
when compileOption("threads"):
|
||||
deallocShared(s.p)
|
||||
else:
|
||||
dealloc(s.p)
|
||||
when compileOption("threads"):
|
||||
deallocShared(s.p)
|
||||
else:
|
||||
dealloc(s.p)
|
||||
@@ -1,591 +0,0 @@
|
||||
#
|
||||
# YRC: Thread-safe ORC (concurrent cycle collector).
|
||||
# Same API as orc.nim but with the global mutator/collector RWLock for safety.
|
||||
# Destructors for refs run at collection time, not immediately on last decRef.
|
||||
# See yrc_proof.lean for a Lean 4 proof of safety and deadlock freedom.
|
||||
#
|
||||
# ## Locking Protocol
|
||||
#
|
||||
# ALL topology-changing operations — heap-field writes (`nimAsgnYrc`,
|
||||
# `nimSinkYrc`) and seq mutations that resize internal buffers — hold the
|
||||
# global mutator read lock (`gYrcGlobalLock` via `acquireMutatorLock`).
|
||||
# Multiple mutators may hold this read lock simultaneously.
|
||||
#
|
||||
# The cycle collector acquires the exclusive write lock for the entire
|
||||
# mark/scan/collect phase. This means the heap topology is *completely
|
||||
# frozen* during collection: no `nimAsgnYrc` or seq operation can mutate
|
||||
# any pointer field while the three passes run. This gives the Bacon
|
||||
# algorithm the stable subgraph it requires without full write barriers.
|
||||
#
|
||||
# Consequence for incRef in `nimAsgnYrc`:
|
||||
# Because the collector is blocked, the incRef can be a direct atomic
|
||||
# increment on the RefHeader (`increment head(src)`) rather than going
|
||||
# through the `toInc` stripe queue. The collector will see the updated
|
||||
# RC immediately when it next acquires the write lock. Only decrements
|
||||
# (`yrcDec`) still use the `toDec` stripe queue so that objects whose RC
|
||||
# might reach zero are handled by the collector's cycle-detection logic.
|
||||
#
|
||||
# ## Why No Write Barrier Is Needed
|
||||
#
|
||||
# The classic concurrent-GC hazard is the "lost object" problem: during
|
||||
# collection the mutator executes `A.field = B` where A is already scanned
|
||||
# (black), B is reachable only through an unscanned (gray) object C, and then
|
||||
# C's reference to B is removed. The collector never discovers B and frees it
|
||||
# while A still points to it. Traditional concurrent collectors need write
|
||||
# barriers to prevent this.
|
||||
#
|
||||
# This problem structurally cannot arise in YRC for two reasons:
|
||||
#
|
||||
# 1. The mutator lock freezes the topology during all three passes, so no
|
||||
# concurrent field write can race with markGray/scan/collectWhite.
|
||||
#
|
||||
# 2. Even without the lock, the cycle collector only frees *closed cycles* —
|
||||
# subgraphs where every reference to every member comes from within the
|
||||
# group, with zero external references. To execute `A.field = B` the
|
||||
# mutator must hold a reference to A (external ref), which `scan` would
|
||||
# rescue. The two conditions are mutually exclusive.
|
||||
#
|
||||
# In practice reason (1) makes reason (2) a belt-and-suspenders safety
|
||||
# argument rather than the primary mechanism.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
include cellseqs_v2
|
||||
|
||||
import std/locks
|
||||
|
||||
const
|
||||
NumStripes = 64
|
||||
QueueSize = 128
|
||||
RootsThreshold = 10
|
||||
|
||||
colBlack = 0b000
|
||||
colGray = 0b001
|
||||
colWhite = 0b010
|
||||
maybeCycle = 0b100
|
||||
inRootsFlag = 0b1000
|
||||
colorMask = 0b011
|
||||
logOrc = defined(nimArcIds)
|
||||
|
||||
type
|
||||
TraceProc = proc (p, env: pointer) {.nimcall, gcsafe, raises: [].}
|
||||
DisposeProc = proc (p: pointer) {.nimcall, gcsafe, raises: [].}
|
||||
|
||||
when defined(nimYrcAtomicIncs):
|
||||
template color(c): untyped = atomicLoadN(addr c.rc, ATOMIC_ACQUIRE) and colorMask
|
||||
template setColor(c, col) =
|
||||
block:
|
||||
var expected = atomicLoadN(addr c.rc, ATOMIC_RELAXED)
|
||||
while true:
|
||||
let desired = (expected and not colorMask) or col
|
||||
if atomicCompareExchangeN(addr c.rc, addr expected, desired, true,
|
||||
ATOMIC_ACQ_REL, ATOMIC_RELAXED):
|
||||
break
|
||||
template loadRc(c): int = atomicLoadN(addr c.rc, ATOMIC_ACQUIRE)
|
||||
template trialDec(c) =
|
||||
discard atomicFetchAdd(addr c.rc, -rcIncrement, ATOMIC_ACQ_REL)
|
||||
template trialInc(c) =
|
||||
discard atomicFetchAdd(addr c.rc, rcIncrement, ATOMIC_ACQ_REL)
|
||||
template rcClearFlag(c, flag) =
|
||||
block:
|
||||
var expected = atomicLoadN(addr c.rc, ATOMIC_RELAXED)
|
||||
while true:
|
||||
let desired = expected and not flag
|
||||
if atomicCompareExchangeN(addr c.rc, addr expected, desired, true,
|
||||
ATOMIC_ACQ_REL, ATOMIC_RELAXED):
|
||||
break
|
||||
template rcSetFlag(c, flag) =
|
||||
block:
|
||||
var expected = atomicLoadN(addr c.rc, ATOMIC_RELAXED)
|
||||
while true:
|
||||
let desired = expected or flag
|
||||
if atomicCompareExchangeN(addr c.rc, addr expected, desired, true,
|
||||
ATOMIC_ACQ_REL, ATOMIC_RELAXED):
|
||||
break
|
||||
else:
|
||||
template color(c): untyped = c.rc and colorMask
|
||||
template setColor(c, col) =
|
||||
when col == colBlack:
|
||||
c.rc = c.rc and not colorMask
|
||||
else:
|
||||
c.rc = c.rc and not colorMask or col
|
||||
template loadRc(c): int = c.rc
|
||||
template trialDec(c) = c.rc = c.rc -% rcIncrement
|
||||
template trialInc(c) = c.rc = c.rc +% rcIncrement
|
||||
template rcClearFlag(c, flag) = c.rc = c.rc and not flag
|
||||
template rcSetFlag(c, flag) = c.rc = c.rc or flag
|
||||
|
||||
const
|
||||
optimizedOrc = false
|
||||
useJumpStack = false
|
||||
|
||||
type
|
||||
GcEnv = object
|
||||
traceStack: CellSeq[ptr pointer]
|
||||
when useJumpStack:
|
||||
jumpStack: CellSeq[ptr pointer]
|
||||
toFree: CellSeq[Cell]
|
||||
freed, touched, edges, rcSum: int
|
||||
keepThreshold: bool
|
||||
|
||||
proc trace(s: Cell; desc: PNimTypeV2; j: var GcEnv) {.inline.} =
|
||||
if desc.traceImpl != nil:
|
||||
var p = s +! sizeof(RefHeader)
|
||||
cast[TraceProc](desc.traceImpl)(p, addr(j))
|
||||
|
||||
type
|
||||
Stripe = object
|
||||
when not defined(yrcAtomics):
|
||||
lockInc: Lock
|
||||
toIncLen: int
|
||||
toInc: array[QueueSize, Cell]
|
||||
lockDec: Lock
|
||||
toDecLen: int
|
||||
toDec: array[QueueSize, (Cell, PNimTypeV2)]
|
||||
|
||||
type
|
||||
PreventThreadFromCollectProc* = proc(): bool {.nimcall, gcsafe, raises: [].}
|
||||
## Callback run before this thread runs the cycle collector.
|
||||
## Return `true` to allow collection, `false` to skip (e.g. real-time thread).
|
||||
## Invoked while holding the global lock; must not call back into YRC.
|
||||
|
||||
var
|
||||
roots: CellSeq[Cell] # merged roots, used under global lock
|
||||
stripes: array[NumStripes, Stripe]
|
||||
rootsThreshold: int = 128
|
||||
defaultThreshold = when defined(nimFixedOrc): 10_000 else: 128
|
||||
gPreventThreadFromCollectProc: PreventThreadFromCollectProc = nil
|
||||
|
||||
proc GC_setPreventThreadFromCollectProc*(cb: PreventThreadFromCollectProc) =
|
||||
##[ Can be used to customize the cycle collector for a thread. For example,
|
||||
to ensure that a hard realtime thread cannot run the cycle collector use:
|
||||
|
||||
```nim
|
||||
var hardRealTimeThread: int
|
||||
GC_setPreventThreadFromCollectProc(proc(): bool {.nimcall.} = hardRealTimeThread == getThreadId())
|
||||
```
|
||||
|
||||
To ensure that a hard realtime thread cannot by involved in any cycle collector activity use:
|
||||
|
||||
```nim
|
||||
GC_setPreventThreadFromCollectProc(proc(): bool {.nimcall.} =
|
||||
if hardRealTimeThread == getThreadId():
|
||||
writeStackTrace()
|
||||
echo "Realtime thread involved in unpredictable cycle collector activity!"
|
||||
result = false
|
||||
)
|
||||
```
|
||||
]##
|
||||
gPreventThreadFromCollectProc = cb
|
||||
|
||||
proc GC_getPreventThreadFromCollectProc*(): PreventThreadFromCollectProc =
|
||||
## Returns the current "prevent thread from collecting proc".
|
||||
## Typically `nil` if not set.
|
||||
result = gPreventThreadFromCollectProc
|
||||
|
||||
proc mayRunCycleCollect(): bool {.inline.} =
|
||||
if gPreventThreadFromCollectProc == nil: true
|
||||
else: not gPreventThreadFromCollectProc()
|
||||
|
||||
proc getStripeIdx(): int {.inline.} =
|
||||
getThreadId() and (NumStripes - 1)
|
||||
|
||||
proc nimIncRefCyclic(p: pointer; cyclic: bool) {.compilerRtl, inl.} =
|
||||
let h = head(p)
|
||||
when optimizedOrc:
|
||||
if cyclic: h.rc = h.rc or maybeCycle
|
||||
when defined(nimYrcAtomicIncs):
|
||||
discard atomicFetchAdd(addr h.rc, rcIncrement, ATOMIC_ACQ_REL)
|
||||
elif defined(yrcAtomics):
|
||||
let s = getStripeIdx()
|
||||
let slot = atomicFetchAdd(addr stripes[s].toIncLen, 1, ATOMIC_ACQ_REL)
|
||||
if slot < QueueSize:
|
||||
atomicStoreN(addr stripes[s].toInc[slot], h, ATOMIC_RELEASE)
|
||||
else:
|
||||
yrcCollectorLock:
|
||||
h.rc = h.rc +% rcIncrement
|
||||
for i in 0..<NumStripes:
|
||||
let len = atomicExchangeN(addr stripes[i].toIncLen, 0, ATOMIC_ACQUIRE)
|
||||
for j in 0..<min(len, QueueSize):
|
||||
let x = atomicLoadN(addr stripes[i].toInc[j], ATOMIC_ACQUIRE)
|
||||
x.rc = x.rc +% rcIncrement
|
||||
else:
|
||||
let idx = getStripeIdx()
|
||||
while true:
|
||||
var overflow = false
|
||||
withLock stripes[idx].lockInc:
|
||||
if stripes[idx].toIncLen < QueueSize:
|
||||
stripes[idx].toInc[stripes[idx].toIncLen] = h
|
||||
stripes[idx].toIncLen += 1
|
||||
else:
|
||||
overflow = true
|
||||
if overflow:
|
||||
yrcCollectorLock:
|
||||
for i in 0..<NumStripes:
|
||||
withLock stripes[i].lockInc:
|
||||
for j in 0..<stripes[i].toIncLen:
|
||||
let x = stripes[i].toInc[j]
|
||||
x.rc = x.rc +% rcIncrement
|
||||
stripes[i].toIncLen = 0
|
||||
else:
|
||||
break
|
||||
|
||||
proc mergePendingRoots() =
|
||||
# Merge buffered RC operations. Note: Unlike truly concurrent collectors,
|
||||
# we don't need to set color to black on incRef because collection runs
|
||||
# under the global lock, so no concurrent mutations happen during collection.
|
||||
for i in 0..<NumStripes:
|
||||
when not defined(nimYrcAtomicIncs):
|
||||
# Inc buffers only exist when increfs are buffered (not atomic)
|
||||
when defined(yrcAtomics):
|
||||
let incLen = atomicExchangeN(addr stripes[i].toIncLen, 0, ATOMIC_ACQUIRE)
|
||||
for j in 0..<min(incLen, QueueSize):
|
||||
let x = atomicLoadN(addr stripes[i].toInc[j], ATOMIC_ACQUIRE)
|
||||
x.rc = x.rc +% rcIncrement
|
||||
else:
|
||||
withLock stripes[i].lockInc:
|
||||
for j in 0..<stripes[i].toIncLen:
|
||||
let x = stripes[i].toInc[j]
|
||||
x.rc = x.rc +% rcIncrement
|
||||
stripes[i].toIncLen = 0
|
||||
withLock stripes[i].lockDec:
|
||||
for j in 0..<stripes[i].toDecLen:
|
||||
let (c, desc) = stripes[i].toDec[j]
|
||||
trialDec(c)
|
||||
if (loadRc(c) and inRootsFlag) == 0:
|
||||
rcSetFlag(c, inRootsFlag)
|
||||
if roots.d == nil: init(roots)
|
||||
add(roots, c, desc)
|
||||
stripes[i].toDecLen = 0
|
||||
|
||||
proc collectCycles()
|
||||
|
||||
when logOrc or orcLeakDetector:
|
||||
proc writeCell(msg: cstring; s: Cell; desc: PNimTypeV2) =
|
||||
when orcLeakDetector:
|
||||
cfprintf(cstderr, "%s %s file: %s:%ld; color: %ld; thread: %ld\n",
|
||||
msg, if desc != nil: desc.name else: cstring"(nil)", s.filename, s.line, s.color, getThreadId())
|
||||
else:
|
||||
# Guard nil desc/desc.name. Use cell pointer as id to avoid uninitialized s.refId (roots may have refId unset)
|
||||
let name = if desc != nil and desc.name != nil: desc.name else: cstring"(null)"
|
||||
cfprintf(cstderr, "%s %s %p isroot: %s; RC: %ld; color: %ld; thread: %ld\n",
|
||||
msg, name, s, (if (s.rc and inRootsFlag) != 0: "yes" else: "no"), s.rc shr rcShift, s.color, getThreadId())
|
||||
|
||||
proc free(s: Cell; desc: PNimTypeV2) {.inline.} =
|
||||
when traceCollector:
|
||||
cprintf("[From ] %p rc %ld color %ld\n", s, loadRc(s) shr rcShift, s.color)
|
||||
if (loadRc(s) and inRootsFlag) == 0:
|
||||
let p = s +! sizeof(RefHeader)
|
||||
when logOrc: writeCell("free", s, desc)
|
||||
if desc.destructor != nil:
|
||||
cast[DestructorProc](desc.destructor)(p)
|
||||
nimRawDispose(p, desc.align)
|
||||
|
||||
template orcAssert(cond, msg) =
|
||||
when logOrc:
|
||||
if not cond:
|
||||
cfprintf(cstderr, "[Bug!] %s\n", msg)
|
||||
rawQuit 1
|
||||
|
||||
proc nimTraceRef(q: pointer; desc: PNimTypeV2; env: pointer) {.compilerRtl, inl.} =
|
||||
let p = cast[ptr pointer](q)
|
||||
if p[] != nil:
|
||||
var j = cast[ptr GcEnv](env)
|
||||
j.traceStack.add(p, desc)
|
||||
|
||||
proc nimTraceRefDyn(q: pointer; env: pointer) {.compilerRtl, inl.} =
|
||||
let p = cast[ptr pointer](q)
|
||||
if p[] != nil:
|
||||
var j = cast[ptr GcEnv](env)
|
||||
j.traceStack.add(p, cast[ptr PNimTypeV2](p[])[])
|
||||
|
||||
proc scanBlack(s: Cell; desc: PNimTypeV2; j: var GcEnv) =
|
||||
s.setColor colBlack
|
||||
let until = j.traceStack.len
|
||||
trace(s, desc, j)
|
||||
when logOrc: writeCell("root still alive", s, desc)
|
||||
while j.traceStack.len > until:
|
||||
let (entry, desc) = j.traceStack.pop()
|
||||
let t = head entry[]
|
||||
trialInc(t)
|
||||
if t.color != colBlack:
|
||||
t.setColor colBlack
|
||||
trace(t, desc, j)
|
||||
when logOrc: writeCell("child still alive", t, desc)
|
||||
|
||||
proc markGray(s: Cell; desc: PNimTypeV2; j: var GcEnv) =
|
||||
if s.color != colGray:
|
||||
s.setColor colGray
|
||||
j.touched = j.touched +% 1
|
||||
j.rcSum = j.rcSum +% (loadRc(s) shr rcShift) +% 1
|
||||
orcAssert(j.traceStack.len == 0, "markGray: trace stack not empty")
|
||||
trace(s, desc, j)
|
||||
while j.traceStack.len > 0:
|
||||
let (entry, desc) = j.traceStack.pop()
|
||||
let t = head entry[]
|
||||
trialDec(t)
|
||||
j.edges = j.edges +% 1
|
||||
if t.color != colGray:
|
||||
t.setColor colGray
|
||||
j.touched = j.touched +% 1
|
||||
j.rcSum = j.rcSum +% (loadRc(t) shr rcShift) +% 2
|
||||
trace(t, desc, j)
|
||||
|
||||
proc scan(s: Cell; desc: PNimTypeV2; j: var GcEnv) =
|
||||
if s.color == colGray:
|
||||
if (loadRc(s) shr rcShift) >= 0:
|
||||
scanBlack(s, desc, j)
|
||||
else:
|
||||
orcAssert(j.traceStack.len == 0, "scan: trace stack not empty")
|
||||
s.setColor(colWhite)
|
||||
trace(s, desc, j)
|
||||
while j.traceStack.len > 0:
|
||||
let (entry, desc) = j.traceStack.pop()
|
||||
let t = head entry[]
|
||||
if t.color == colGray:
|
||||
if (loadRc(t) shr rcShift) >= 0:
|
||||
scanBlack(t, desc, j)
|
||||
else:
|
||||
t.setColor(colWhite)
|
||||
trace(t, desc, j)
|
||||
|
||||
proc collectColor(s: Cell; desc: PNimTypeV2; col: int; j: var GcEnv) =
|
||||
if s.color == col and (loadRc(s) and inRootsFlag) == 0:
|
||||
orcAssert(j.traceStack.len == 0, "collectWhite: trace stack not empty")
|
||||
s.setColor(colBlack)
|
||||
j.toFree.add(s, desc)
|
||||
trace(s, desc, j)
|
||||
while j.traceStack.len > 0:
|
||||
let (entry, desc) = j.traceStack.pop()
|
||||
let t = head entry[]
|
||||
entry[] = nil
|
||||
if t.color == col and (loadRc(t) and inRootsFlag) == 0:
|
||||
j.toFree.add(t, desc)
|
||||
t.setColor(colBlack)
|
||||
trace(t, desc, j)
|
||||
|
||||
proc collectCyclesBacon(j: var GcEnv; lowMark: int) =
|
||||
# YRC defers all destruction to collection time - process ALL roots through Bacon's algorithm
|
||||
# This is different from ORC which handles immediate garbage (rc == 0) directly
|
||||
if lockState == Collecting:
|
||||
return
|
||||
lockState = Collecting
|
||||
let last = roots.len -% 1
|
||||
when logOrc:
|
||||
for i in countdown(last, lowMark):
|
||||
writeCell("root", roots.d[i][0], roots.d[i][1])
|
||||
|
||||
# Process all roots through markGray (Bacon's algorithm)
|
||||
for i in countdown(last, lowMark):
|
||||
markGray(roots.d[i][0], roots.d[i][1], j)
|
||||
|
||||
var colToCollect = colWhite
|
||||
if j.rcSum == j.edges:
|
||||
# Short-cut: we know everything is garbage
|
||||
colToCollect = colGray
|
||||
j.keepThreshold = true
|
||||
else:
|
||||
# Normal scan phase
|
||||
for i in countdown(last, lowMark):
|
||||
scan(roots.d[i][0], roots.d[i][1], j)
|
||||
|
||||
# Collect phase: free all garbage objects
|
||||
init j.toFree
|
||||
for i in 0 ..< roots.len:
|
||||
let s = roots.d[i][0]
|
||||
rcClearFlag(s, inRootsFlag)
|
||||
collectColor(s, roots.d[i][1], colToCollect, j)
|
||||
|
||||
# Clear roots before freeing to prevent nested collectCycles() from accessing freed cells
|
||||
roots.len = 0
|
||||
|
||||
# Free all collected objects
|
||||
# Destructors must not call nimDecRefIsLastCyclicStatic (add to toDec) during this phase
|
||||
for i in 0 ..< j.toFree.len:
|
||||
let s = j.toFree.d[i][0]
|
||||
when orcLeakDetector:
|
||||
writeCell("CYCLIC OBJECT FREED", s, j.toFree.d[i][1])
|
||||
free(s, j.toFree.d[i][1])
|
||||
j.freed = j.freed +% j.toFree.len
|
||||
deinit j.toFree
|
||||
|
||||
when defined(nimOrcStats):
|
||||
var freedCyclicObjects {.threadvar.}: int
|
||||
|
||||
proc collectCycles() =
|
||||
when logOrc:
|
||||
cfprintf(cstderr, "[collectCycles] begin\n")
|
||||
yrcCollectorLock:
|
||||
mergePendingRoots()
|
||||
if roots.len >= rootsThreshold and mayRunCycleCollect():
|
||||
let nRoots = roots.len
|
||||
var j: GcEnv
|
||||
init j.traceStack
|
||||
collectCyclesBacon(j, 0)
|
||||
if roots.len == 0 and roots.d != nil:
|
||||
deinit roots
|
||||
when not defined(nimStressOrc):
|
||||
if j.keepThreshold:
|
||||
discard
|
||||
elif j.freed *% 2 >= j.touched:
|
||||
when not defined(nimFixedOrc):
|
||||
rootsThreshold = max(rootsThreshold div 3 *% 2, 16)
|
||||
else:
|
||||
rootsThreshold = 0
|
||||
elif rootsThreshold < high(int) div 4:
|
||||
rootsThreshold = (if rootsThreshold <= 0: defaultThreshold else: rootsThreshold)
|
||||
rootsThreshold = rootsThreshold div 2 +% rootsThreshold
|
||||
# Cost-aware: if this run was expensive (large graph), raise threshold more so we don't run again too soon
|
||||
if j.touched > nRoots *% 4:
|
||||
rootsThreshold = rootsThreshold div 2 +% rootsThreshold
|
||||
rootsThreshold = min(rootsThreshold, defaultThreshold *% 16)
|
||||
rootsThreshold = min(rootsThreshold, nRoots *% 2)
|
||||
when logOrc:
|
||||
cfprintf(cstderr, "[collectCycles] end; freed %ld new threshold %ld\n", j.freed, rootsThreshold)
|
||||
when defined(nimOrcStats):
|
||||
inc freedCyclicObjects, j.freed
|
||||
deinit j.traceStack
|
||||
|
||||
when defined(nimOrcStats):
|
||||
type
|
||||
OrcStats* = object
|
||||
freedCyclicObjects*: int
|
||||
proc GC_orcStats*(): OrcStats =
|
||||
result = OrcStats(freedCyclicObjects: freedCyclicObjects)
|
||||
|
||||
proc GC_runOrc* =
|
||||
yrcCollectorLock:
|
||||
mergePendingRoots()
|
||||
if roots.len > 0 and mayRunCycleCollect():
|
||||
var j: GcEnv
|
||||
init j.traceStack
|
||||
collectCyclesBacon(j, 0)
|
||||
deinit j.traceStack
|
||||
roots.len = 0
|
||||
when logOrc: orcAssert roots.len == 0, "roots not empty!"
|
||||
|
||||
proc GC_enableOrc*() =
|
||||
when not defined(nimStressOrc):
|
||||
rootsThreshold = 0
|
||||
|
||||
proc GC_disableOrc*() =
|
||||
when not defined(nimStressOrc):
|
||||
rootsThreshold = high(int)
|
||||
|
||||
proc GC_prepareOrc*(): int {.inline.} =
|
||||
yrcCollectorLock:
|
||||
mergePendingRoots()
|
||||
result = roots.len
|
||||
|
||||
proc GC_partialCollect*(limit: int) =
|
||||
yrcCollectorLock:
|
||||
mergePendingRoots()
|
||||
if roots.len > limit and mayRunCycleCollect():
|
||||
var j: GcEnv
|
||||
init j.traceStack
|
||||
collectCyclesBacon(j, limit)
|
||||
deinit j.traceStack
|
||||
roots.len = limit
|
||||
|
||||
proc GC_fullCollect* =
|
||||
GC_runOrc()
|
||||
|
||||
proc GC_enableMarkAndSweep*() = GC_enableOrc()
|
||||
proc GC_disableMarkAndSweep*() = GC_disableOrc()
|
||||
|
||||
const acyclicFlag = 1
|
||||
|
||||
when optimizedOrc:
|
||||
template markedAsCyclic(s: Cell; desc: PNimTypeV2): bool =
|
||||
(desc.flags and acyclicFlag) == 0 and (s.rc and maybeCycle) != 0
|
||||
else:
|
||||
template markedAsCyclic(s: Cell; desc: PNimTypeV2): bool =
|
||||
(desc.flags and acyclicFlag) == 0
|
||||
|
||||
proc nimDecRefIsLastCyclicDyn(p: pointer): bool {.compilerRtl, inl.} =
|
||||
result = false
|
||||
if p != nil:
|
||||
let cell = head(p)
|
||||
let desc = cast[ptr PNimTypeV2](p)[]
|
||||
let idx = getStripeIdx()
|
||||
while true:
|
||||
var overflow = false
|
||||
withLock stripes[idx].lockDec:
|
||||
if stripes[idx].toDecLen < QueueSize:
|
||||
stripes[idx].toDec[stripes[idx].toDecLen] = (cell, desc)
|
||||
stripes[idx].toDecLen += 1
|
||||
else:
|
||||
overflow = true
|
||||
if overflow:
|
||||
collectCycles()
|
||||
else:
|
||||
break
|
||||
|
||||
proc nimDecRefIsLastDyn(p: pointer): bool {.compilerRtl, inl.} =
|
||||
nimDecRefIsLastCyclicDyn(p)
|
||||
|
||||
proc nimDecRefIsLastCyclicStatic(p: pointer; desc: PNimTypeV2): bool {.compilerRtl, inl.} =
|
||||
result = false
|
||||
if p != nil:
|
||||
let cell = head(p)
|
||||
let idx = getStripeIdx()
|
||||
while true:
|
||||
var overflow = false
|
||||
withLock stripes[idx].lockDec:
|
||||
if stripes[idx].toDecLen < QueueSize:
|
||||
stripes[idx].toDec[stripes[idx].toDecLen] = (cell, desc)
|
||||
stripes[idx].toDecLen += 1
|
||||
else:
|
||||
overflow = true
|
||||
if overflow:
|
||||
collectCycles()
|
||||
else:
|
||||
break
|
||||
|
||||
proc unsureAsgnRef(dest: ptr pointer, src: pointer) {.inline.} =
|
||||
dest[] = src
|
||||
if src != nil: nimIncRefCyclic(src, true)
|
||||
|
||||
proc yrcDec(tmp: pointer; desc: PNimTypeV2) {.inline.} =
|
||||
if desc != nil:
|
||||
discard nimDecRefIsLastCyclicStatic(tmp, desc)
|
||||
else:
|
||||
discard nimDecRefIsLastCyclicDyn(tmp)
|
||||
|
||||
proc nimAsgnYrc(dest: ptr pointer; src: pointer; desc: PNimTypeV2) {.compilerRtl.} =
|
||||
## YRC write barrier for ref copy assignment.
|
||||
## Holds the mutator read lock for the entire operation so the collector
|
||||
## cannot run between the incRef and decRef, closing the stale-decRef
|
||||
## bug. Direct atomic incRef replaces the toInc stripe queue: the
|
||||
## collector is blocked, so the RC update is immediately visible and correct.
|
||||
acquireMutatorLock()
|
||||
if src != nil: increment head(src) # direct atomic: no toInc queue needed
|
||||
let tmp = dest[]
|
||||
dest[] = src
|
||||
if tmp != nil: yrcDec(tmp, desc) # still deferred via toDec for cycle detection
|
||||
releaseMutatorLock()
|
||||
|
||||
proc nimSinkYrc(dest: ptr pointer; src: pointer; desc: PNimTypeV2) {.compilerRtl.} =
|
||||
## YRC write barrier for ref sink (move). No incRef on source.
|
||||
acquireMutatorLock()
|
||||
let tmp = dest[]
|
||||
dest[] = src
|
||||
if tmp != nil: yrcDec(tmp, desc)
|
||||
releaseMutatorLock()
|
||||
|
||||
proc nimMarkCyclic(p: pointer) {.compilerRtl, inl.} =
|
||||
when optimizedOrc:
|
||||
if p != nil:
|
||||
let h = head(p)
|
||||
h.rc = h.rc or maybeCycle
|
||||
|
||||
# Initialize locks at module load.
|
||||
# RwLock stripes live in seqs_v2 (gYrcLocks); NumLockStripes is exported from there.
|
||||
for i in 0..<NumLockStripes:
|
||||
initRwLock(gYrcLocks[i].lock)
|
||||
for i in 0..<NumStripes:
|
||||
when not defined(yrcAtomics) and not defined(nimYrcAtomicIncs):
|
||||
initLock(stripes[i].lockInc)
|
||||
initLock(stripes[i].lockDec)
|
||||
|
||||
{.pop.}
|
||||
@@ -1,353 +0,0 @@
|
||||
/-
|
||||
YRC Safety Proof (self-contained, no Mathlib)
|
||||
==============================================
|
||||
Formal model of YRC's key invariant: the cycle collector never frees
|
||||
an object that any mutator thread can reach.
|
||||
|
||||
## Model overview
|
||||
|
||||
We model the heap as a set of objects with directed edges (ref fields).
|
||||
Each thread owns a set of *stack roots* — objects reachable from local variables.
|
||||
The write barrier (nimAsgnYrc) does:
|
||||
1. atomic store dest ← src (graph is immediately current)
|
||||
2. buffer inc(src) (deferred)
|
||||
3. buffer dec(old) (deferred)
|
||||
|
||||
The collector (under global lock) does:
|
||||
1. Merge all buffered inc/dec into merged RCs
|
||||
2. Trial deletion (markGray): subtract internal edges from merged RCs
|
||||
3. scan: objects with RC ≥ 0 after trial deletion are rescued (scanBlack)
|
||||
4. Free objects that remain white (closed cycles with zero external refs)
|
||||
-/
|
||||
|
||||
-- Objects and threads are just natural numbers for simplicity.
|
||||
abbrev Obj := Nat
|
||||
abbrev Thread := Nat
|
||||
|
||||
/-! ### State -/
|
||||
|
||||
/-- The state of the heap and collector at a point in time. -/
|
||||
structure State where
|
||||
/-- Physical heap edges: `edges x y` means object `x` has a ref field pointing to `y`.
|
||||
Always up-to-date (atomic stores). -/
|
||||
edges : Obj → Obj → Prop
|
||||
/-- Stack roots per thread. `roots t x` means thread `t` has a local variable pointing to `x`. -/
|
||||
roots : Thread → Obj → Prop
|
||||
/-- Pending buffered increments (not yet merged). -/
|
||||
pendingInc : Obj → Nat
|
||||
/-- Pending buffered decrements (not yet merged). -/
|
||||
pendingDec : Obj → Nat
|
||||
|
||||
/-! ### Reachability -/
|
||||
|
||||
/-- An object is *reachable* if some thread can reach it via stack roots + heap edges. -/
|
||||
inductive Reachable (s : State) : Obj → Prop where
|
||||
| root (t : Thread) (x : Obj) : s.roots t x → Reachable s x
|
||||
| step (x y : Obj) : Reachable s x → s.edges x y → Reachable s y
|
||||
|
||||
/-- Directed reachability between heap objects (following physical edges only). -/
|
||||
inductive HeapReachable (s : State) : Obj → Obj → Prop where
|
||||
| refl (x : Obj) : HeapReachable s x x
|
||||
| step (x y z : Obj) : HeapReachable s x y → s.edges y z → HeapReachable s x z
|
||||
|
||||
/-- If a root reaches `r` and `r` heap-reaches `x`, then `x` is Reachable. -/
|
||||
theorem heapReachable_of_reachable (s : State) (r x : Obj)
|
||||
(hr : Reachable s r) (hp : HeapReachable s r x) :
|
||||
Reachable s x := by
|
||||
induction hp with
|
||||
| refl => exact hr
|
||||
| step _ _ _ hedge ih => exact Reachable.step _ _ ih hedge
|
||||
|
||||
/-! ### What the collector frees -/
|
||||
|
||||
/-- An object has an *external reference* if some thread's stack roots point to it. -/
|
||||
def hasExternalRef (s : State) (x : Obj) : Prop :=
|
||||
∃ t, s.roots t x
|
||||
|
||||
/-- An object is *externally anchored* if it is heap-reachable from some
|
||||
object that has an external reference. This is what scanBlack computes:
|
||||
it starts from objects with trialRC ≥ 0 (= has external refs) and traces
|
||||
the current physical graph. -/
|
||||
def anchored (s : State) (x : Obj) : Prop :=
|
||||
∃ r, hasExternalRef s r ∧ HeapReachable s r x
|
||||
|
||||
/-- The collector frees `x` only if `x` is *not anchored*:
|
||||
no external ref, and not reachable from any externally-referenced object.
|
||||
This models: after trial deletion, x remained white, and scanBlack
|
||||
didn't rescue it. -/
|
||||
def collectorFrees (s : State) (x : Obj) : Prop :=
|
||||
¬ anchored s x
|
||||
|
||||
/-! ### Main safety theorem -/
|
||||
|
||||
/-- **Lemma**: Every reachable object is anchored.
|
||||
If thread `t` reaches `x`, then there is a chain from a stack root
|
||||
(which has an external ref) through heap edges to `x`. -/
|
||||
theorem reachable_is_anchored (s : State) (x : Obj)
|
||||
(h : Reachable s x) : anchored s x := by
|
||||
induction h with
|
||||
| root t x hroot =>
|
||||
exact ⟨x, ⟨t, hroot⟩, HeapReachable.refl x⟩
|
||||
| step a b h_reach_a h_edge ih =>
|
||||
obtain ⟨r, h_ext_r, h_path_r_a⟩ := ih
|
||||
exact ⟨r, h_ext_r, HeapReachable.step r a b h_path_r_a h_edge⟩
|
||||
|
||||
/-- **Main Safety Theorem**: If the collector frees `x`, then no thread
|
||||
can reach `x`. Freed objects are unreachable.
|
||||
|
||||
This is the contrapositive of `reachable_is_anchored`. -/
|
||||
theorem yrc_safety (s : State) (x : Obj)
|
||||
(h_freed : collectorFrees s x) : ¬ Reachable s x := by
|
||||
intro h_reach
|
||||
exact h_freed (reachable_is_anchored s x h_reach)
|
||||
|
||||
/-! ### The write barrier preserves reachability -/
|
||||
|
||||
/-- Model of `nimAsgnYrc(dest_field_of_a, src)`:
|
||||
Object `a` had a field pointing to `old`, now points to `src`.
|
||||
Graph update is immediate. The new edge takes priority (handles src = old). -/
|
||||
def writeBarrier (s : State) (a old src : Obj) : State :=
|
||||
{ s with
|
||||
edges := fun x y =>
|
||||
if x = a ∧ y = src then True
|
||||
else if x = a ∧ y = old then False
|
||||
else s.edges x y
|
||||
pendingInc := fun x => if x = src then s.pendingInc x + 1 else s.pendingInc x
|
||||
pendingDec := fun x => if x = old then s.pendingDec x + 1 else s.pendingDec x }
|
||||
|
||||
/-- **No Lost Object Theorem**: If thread `t` holds a stack ref to `a` and
|
||||
executes `a.field = b` (replacing old), then `b` is reachable afterward.
|
||||
|
||||
This is why the "lost object" problem from concurrent GC literature
|
||||
doesn't arise in YRC: the atomic store makes `a→b` visible immediately,
|
||||
and `a` is anchored (thread `t` holds it), so scanBlack traces `a→b`
|
||||
and rescues `b`. -/
|
||||
theorem no_lost_object (s : State) (t : Thread) (a old b : Obj)
|
||||
(h_root_a : s.roots t a) :
|
||||
Reachable (writeBarrier s a old b) b := by
|
||||
apply Reachable.step a b
|
||||
· exact Reachable.root t a h_root_a
|
||||
· simp [writeBarrier]
|
||||
|
||||
/-! ### Non-atomic write barrier window safety
|
||||
|
||||
The write barrier does three steps non-atomically:
|
||||
1. atomicStore(dest, src) — graph update
|
||||
2. buffer inc(src) — deferred
|
||||
3. buffer dec(old) — deferred
|
||||
|
||||
If the collector runs between steps 1 and 2 (inc not yet buffered):
|
||||
- src has a new incoming heap edge not yet reflected in RCs
|
||||
- But src is reachable from the mutator's stack (mutator held a ref to store it)
|
||||
- So src has an external ref → trialRC ≥ 1 → scanBlack rescues src ✓
|
||||
|
||||
If the collector runs between steps 2 and 3 (dec not yet buffered):
|
||||
- old's RC is inflated by 1 (the dec hasn't arrived)
|
||||
- This is conservative: old appears to have more refs than it does
|
||||
- Trial deletion won't spuriously free it ✓
|
||||
-/
|
||||
|
||||
/-- Model the state between steps 1-2: graph updated, inc not yet buffered.
|
||||
`src` has new edge but RC doesn't reflect it yet. -/
|
||||
def stateAfterStore (s : State) (a old src : Obj) : State :=
|
||||
{ s with
|
||||
edges := fun x y =>
|
||||
if x = a ∧ y = src then True
|
||||
else if x = a ∧ y = old then False
|
||||
else s.edges x y }
|
||||
|
||||
/-- Even in the window between atomic store and buffered inc,
|
||||
src is still reachable (from the mutator's stack via a→src). -/
|
||||
theorem src_reachable_in_window (s : State) (t : Thread) (a old src : Obj)
|
||||
(h_root_a : s.roots t a) :
|
||||
Reachable (stateAfterStore s a old src) src := by
|
||||
apply Reachable.step a src
|
||||
· exact Reachable.root t a h_root_a
|
||||
· simp [stateAfterStore]
|
||||
|
||||
/-- Therefore src is anchored in the window → collector won't free it. -/
|
||||
theorem src_safe_in_window (s : State) (t : Thread) (a old src : Obj)
|
||||
(h_root_a : s.roots t a) :
|
||||
¬ collectorFrees (stateAfterStore s a old src) src := by
|
||||
intro h_freed
|
||||
exact h_freed (reachable_is_anchored _ _ (src_reachable_in_window s t a old src h_root_a))
|
||||
|
||||
/-! ### Deadlock freedom
|
||||
|
||||
YRC uses three classes of locks:
|
||||
• gYrcGlobalLock (level 0)
|
||||
• stripes[i].lockInc (level 2*i + 1, for i in 0..N-1)
|
||||
• stripes[i].lockDec (level 2*i + 2, for i in 0..N-1)
|
||||
|
||||
Total order: global < lockInc[0] < lockDec[0] < lockInc[1] < lockDec[1] < ...
|
||||
|
||||
Every code path in yrc.nim acquires locks in strictly ascending level order:
|
||||
|
||||
**nimIncRefCyclic** (mutator fast path):
|
||||
acquire lockInc[myStripe] → release → done.
|
||||
Holds exactly one lock. ✓
|
||||
|
||||
**nimIncRefCyclic** (overflow path):
|
||||
acquire gYrcGlobalLock (level 0), then for i=0..N-1: acquire lockInc[i] → release.
|
||||
Ascending: 0 < 1 < 3 < 5 < ... ✓
|
||||
|
||||
**nimDecRefIsLastCyclic{Dyn,Static}** (fast path):
|
||||
acquire lockDec[myStripe] → release → done.
|
||||
Holds exactly one lock. ✓
|
||||
|
||||
**nimDecRefIsLastCyclic{Dyn,Static}** (overflow path):
|
||||
calls collectCycles → acquire gYrcGlobalLock (level 0),
|
||||
then mergePendingRoots which for i=0..N-1:
|
||||
acquire lockInc[i] → release, acquire lockDec[i] → release.
|
||||
Ascending: 0 < 1 < 2 < 3 < 4 < ... ✓
|
||||
|
||||
**collectCycles / GC_runOrc** (collector):
|
||||
acquire gYrcGlobalLock (level 0),
|
||||
then mergePendingRoots (same ascending pattern as above). ✓
|
||||
|
||||
**nimAsgnYrc / nimSinkYrc** (write barrier):
|
||||
Calls nimIncRefCyclic then nimDecRefIsLastCyclic*.
|
||||
Each call acquires and releases its lock independently.
|
||||
No nesting between the two calls. ✓
|
||||
|
||||
Since every path follows the total order, deadlock is impossible.
|
||||
-/
|
||||
|
||||
/-- Lock levels in YRC. Each lock maps to a unique natural number. -/
|
||||
inductive LockId (n : Nat) where
|
||||
| global : LockId n
|
||||
| lockInc (i : Nat) (h : i < n) : LockId n
|
||||
| lockDec (i : Nat) (h : i < n) : LockId n
|
||||
|
||||
/-- The level (priority) of each lock in the total order. -/
|
||||
def lockLevel {n : Nat} : LockId n → Nat
|
||||
| .global => 0
|
||||
| .lockInc i _ => 2 * i + 1
|
||||
| .lockDec i _ => 2 * i + 2
|
||||
|
||||
/-- All lock levels are distinct (the level function is injective). -/
|
||||
theorem lockLevel_injective {n : Nat} (a b : LockId n)
|
||||
(h : lockLevel a = lockLevel b) : a = b := by
|
||||
cases a with
|
||||
| global =>
|
||||
cases b with
|
||||
| global => rfl
|
||||
| lockInc j hj => simp [lockLevel] at h
|
||||
| lockDec j hj => simp [lockLevel] at h
|
||||
| lockInc i hi =>
|
||||
cases b with
|
||||
| global => simp [lockLevel] at h
|
||||
| lockInc j hj =>
|
||||
have : i = j := by simp [lockLevel] at h; omega
|
||||
subst this; rfl
|
||||
| lockDec j hj => simp [lockLevel] at h; omega
|
||||
| lockDec i hi =>
|
||||
cases b with
|
||||
| global => simp [lockLevel] at h
|
||||
| lockInc j hj => simp [lockLevel] at h; omega
|
||||
| lockDec j hj =>
|
||||
have : i = j := by simp [lockLevel] at h; omega
|
||||
subst this; rfl
|
||||
|
||||
/-- Helper: stripe lock levels are strictly ascending across stripes. -/
|
||||
theorem stripe_levels_ascending (i : Nat) :
|
||||
2 * i + 1 < 2 * i + 2 ∧ 2 * i + 2 < 2 * (i + 1) + 1 := by
|
||||
constructor <;> omega
|
||||
|
||||
/-- lockInc levels are strictly ascending with index. -/
|
||||
theorem lockInc_level_strict_mono {n : Nat} (i j : Nat) (hi : i < n) (hj : j < n)
|
||||
(hij : i < j) : lockLevel (.lockInc i hi : LockId n) < lockLevel (.lockInc j hj) := by
|
||||
simp [lockLevel]; omega
|
||||
|
||||
/-- lockDec levels are strictly ascending with index. -/
|
||||
theorem lockDec_level_strict_mono {n : Nat} (i j : Nat) (hi : i < n) (hj : j < n)
|
||||
(hij : i < j) : lockLevel (.lockDec i hi : LockId n) < lockLevel (.lockDec j hj) := by
|
||||
simp [lockLevel]; omega
|
||||
|
||||
/-- Global lock has the lowest level (level 0). -/
|
||||
theorem global_level_min {n : Nat} (l : LockId n) (h : l ≠ .global) :
|
||||
lockLevel (.global : LockId n) < lockLevel l := by
|
||||
cases l with
|
||||
| global => exact absurd rfl h
|
||||
| lockInc i hi => simp [lockLevel]
|
||||
| lockDec i hi => simp [lockLevel]
|
||||
|
||||
/-- **Deadlock Freedom**: Any sequence of lock acquisitions that follows the
|
||||
"acquire in ascending level order" discipline cannot deadlock.
|
||||
|
||||
This is a standard result: a total order on locks with the invariant that
|
||||
every thread acquires locks in strictly ascending order prevents cycles
|
||||
in the wait-for graph, which is necessary and sufficient for deadlock.
|
||||
|
||||
We prove the 2-thread case (the general N-thread case follows by the
|
||||
same transitivity argument on the wait-for cycle). -/
|
||||
theorem no_deadlock_from_total_order {n : Nat}
|
||||
-- Two threads each hold a lock and wait for another
|
||||
(held₁ waited₁ held₂ waited₂ : LockId n)
|
||||
-- Thread 1 holds held₁ and wants waited₁ (ascending order)
|
||||
(h1 : lockLevel held₁ < lockLevel waited₁)
|
||||
-- Thread 2 holds held₂ and wants waited₂ (ascending order)
|
||||
(h2 : lockLevel held₂ < lockLevel waited₂)
|
||||
-- Deadlock requires: thread 1 waits for what thread 2 holds,
|
||||
-- and thread 2 waits for what thread 1 holds
|
||||
(h_wait1 : waited₁ = held₂)
|
||||
(h_wait2 : waited₂ = held₁) :
|
||||
False := by
|
||||
subst h_wait1; subst h_wait2
|
||||
omega
|
||||
|
||||
/-! ### Summary of verified properties (all QED, no sorry)
|
||||
|
||||
1. `reachable_is_anchored`: Every reachable object is anchored
|
||||
(has a path from an externally-referenced object via heap edges).
|
||||
|
||||
2. `yrc_safety`: The collector only frees unanchored objects,
|
||||
which are unreachable by all threads. **No use-after-free.**
|
||||
|
||||
3. `no_lost_object`: After `a.field = b`, `b` is reachable
|
||||
(atomic store makes the edge visible immediately).
|
||||
|
||||
4. `src_safe_in_window`: Even between the atomic store and
|
||||
the buffered inc, the collector cannot free src.
|
||||
|
||||
5. `lockLevel_injective`: All lock levels are distinct (well-defined total order).
|
||||
|
||||
6. `global_level_min`: The global lock has the lowest level.
|
||||
|
||||
7. `lockInc_level_strict_mono`, `lockDec_level_strict_mono`:
|
||||
Stripe locks are strictly ordered by index.
|
||||
|
||||
8. `no_deadlock_from_total_order`: A 2-thread deadlock cycle is impossible
|
||||
when both threads acquire locks in ascending level order.
|
||||
|
||||
Together these establish that YRC's write barrier protocol
|
||||
(atomic store → buffer inc → buffer dec) is safe under concurrent
|
||||
collection, and the locking discipline prevents deadlock.
|
||||
|
||||
## What is NOT proved: Completeness (liveness)
|
||||
|
||||
This proof covers **safety** (no use-after-free) and **deadlock-freedom**,
|
||||
but does NOT prove **completeness** — that all garbage cycles are eventually
|
||||
collected.
|
||||
|
||||
Completeness depends on the trial deletion algorithm (Bacon 2001) correctly
|
||||
identifying closed cycles. Specifically it requires proving:
|
||||
|
||||
1. After `mergePendingRoots`, merged RCs equal logical RCs
|
||||
(buffered inc/dec exactly compensate graph changes since last merge).
|
||||
2. `markGray` subtracts exactly the internal (heap→heap) edge count from
|
||||
each node's merged RC, yielding `trialRC(x) = externalRefCount(x)`.
|
||||
3. `scan` correctly partitions: nodes with `trialRC ≥ 0` are rescued by
|
||||
`scanBlack`; nodes with `trialRC < 0` remain white.
|
||||
4. White nodes form closed subgraphs with zero external refs → garbage.
|
||||
|
||||
These properties follow from the well-known Bacon trial-deletion algorithm
|
||||
and are assumed here rather than re-proved. The YRC-specific contribution
|
||||
(buffered RCs, striped queues, concurrent mutators) is what our safety
|
||||
proof covers — showing that concurrency does not break the preconditions
|
||||
that trial deletion relies on (physical graph consistency, eventual RC
|
||||
consistency after merge).
|
||||
|
||||
Reference: D.F. Bacon and V.T. Rajan, "Concurrent Cycle Collection in
|
||||
Reference Counted Systems", ECOOP 2001.
|
||||
-/
|
||||
@@ -1,953 +0,0 @@
|
||||
---- MODULE yrc_proof ----
|
||||
\* TLA+ specification of YRC (Thread-safe ORC cycle collector)
|
||||
\* Models the fine details of barriers, striped queues, and synchronization
|
||||
\*
|
||||
\* ## Key Barrier Semantics Modeled
|
||||
\*
|
||||
\* ### Write Barrier (nimAsgnYrc)
|
||||
\* 1. atomicStoreN(dest, src, ATOMIC_RELEASE)
|
||||
\* - Graph update is immediately visible to all threads (including collector)
|
||||
\* - ATOMIC_RELEASE ensures all prior writes are visible before this store
|
||||
\* - No lock required for graph updates (lock-free)
|
||||
\*
|
||||
\* 2. nimIncRefCyclic(src, true)
|
||||
\* - Acquires per-stripe lockInc[stripe] (fine-grained)
|
||||
\* - Buffers increment in toInc[stripe] queue
|
||||
\* - On overflow: acquires global lock, merges all stripes, applies increment
|
||||
\*
|
||||
\* 3. yrcDec(tmp, desc)
|
||||
\* - Acquires per-stripe lockDec[stripe] (fine-grained)
|
||||
\* - Buffers decrement in toDec[stripe] queue
|
||||
\* - On overflow: acquires global lock, merges all stripes, applies decrement,
|
||||
\* adds to roots array if not already present
|
||||
\*
|
||||
\* ### Merge Operation (mergePendingRoots)
|
||||
\* - Acquires global lock (exclusive access)
|
||||
\* - Sequentially acquires each stripe's lockInc and lockDec
|
||||
\* - Drains all buffers, applies RC adjustments
|
||||
\* - Adds decremented objects to roots array
|
||||
\* - After merge: mergedRC = logicalRC (current graph state)
|
||||
\*
|
||||
\* ### Collection Cycle (under global lock)
|
||||
\* 1. mergePendingRoots: reconcile buffered changes
|
||||
\* 2. markGray: trial deletion (subtract internal edges)
|
||||
\* 3. scan: rescue objects with RC >= 0 (scanBlack follows current graph)
|
||||
\* 4. collectColor: free white objects (closed cycles)
|
||||
\*
|
||||
\* ## Safety Argument
|
||||
\*
|
||||
\* The collector only frees closed cycles (zero external refs). Concurrent writes
|
||||
\* cannot cause "lost objects" because:
|
||||
\* - Graph updates are atomic and immediately visible
|
||||
\* - Mutator must hold stack ref to modify object (external ref)
|
||||
\* - scanBlack follows current physical edges (rescues newly written objects)
|
||||
\* - Only objects unreachable from any stack root are freed
|
||||
\*
|
||||
\* ## Seq Payload Race and RWLock Fix
|
||||
\*
|
||||
\* Value types like seq[T] (where T can form cycles) have internal heap
|
||||
\* allocations (data arrays / "payloads") that are freed by value-type
|
||||
\* hooks (=sink, =destroy), NOT by the cycle collector. This creates a race:
|
||||
\*
|
||||
\* 1. Object O has a seq field with payload P containing refs
|
||||
\* 2. Collector starts tracing O -- reads payload pointer P
|
||||
\* 3. Mutator does O.seq = newSeq -- frees P (value-type destructor)
|
||||
\* 4. Collector dereferences P -- use-after-free!
|
||||
\*
|
||||
\* Fix: Change the global YRC lock to a read-write lock (RWLock).
|
||||
\* - Collector acquires the WRITE lock (exclusive access during tracing)
|
||||
\* - Seq mutations (assign, setLen, add, etc.) acquire the READ lock
|
||||
\* - Multiple seq mutations can proceed concurrently (read lock is shared)
|
||||
\* - But seq mutations block while the collector traces (write lock is exclusive)
|
||||
\*
|
||||
\* This prevents the race: the mutator cannot free a payload while the
|
||||
\* collector is tracing it, because acquiring the read lock requires
|
||||
\* the write lock to be unheld.
|
||||
\*
|
||||
\* Deadlock avoidance: If a seq operation triggers collectCycles() via stripe
|
||||
\* overflow while already holding the read lock, it must NOT attempt to
|
||||
\* acquire the write lock. Instead, it should drain the overflow buffers
|
||||
\* without running the full collection cycle.
|
||||
|
||||
EXTENDS Naturals, Integers, Sequences, FiniteSets, TLC
|
||||
|
||||
CONSTANTS NumStripes, QueueSize, RootsThreshold, Objects, Threads, ObjTypes
|
||||
ASSUME NumStripes \in Nat /\ NumStripes > 0
|
||||
ASSUME QueueSize \in Nat /\ QueueSize > 0
|
||||
ASSUME RootsThreshold \in Nat
|
||||
ASSUME IsFiniteSet(Objects)
|
||||
ASSUME IsFiniteSet(Threads)
|
||||
ASSUME IsFiniteSet(ObjTypes)
|
||||
|
||||
\* Seq payload identifiers (models heap-allocated data arrays of seq[T])
|
||||
CONSTANTS SeqPayloads
|
||||
ASSUME IsFiniteSet(SeqPayloads)
|
||||
|
||||
\* NULL constant (represents "no thread" for locks)
|
||||
\* We use a sentinel value that's guaranteed not to be in Threads or Objects
|
||||
NULL == "NULL" \* String literal that won't conflict with Threads/Objects
|
||||
ASSUME NULL \notin Threads /\ NULL \notin Objects /\ NULL \notin SeqPayloads
|
||||
|
||||
\* Helper functions
|
||||
\* Note: GetStripeIdx is not used, GetStripe is used instead
|
||||
|
||||
\* Color constants
|
||||
colBlack == 0
|
||||
colGray == 1
|
||||
colWhite == 2
|
||||
maybeCycle == 4
|
||||
inRootsFlag == 8
|
||||
colorMask == 3
|
||||
|
||||
\* State variables
|
||||
VARIABLES
|
||||
\* Physical heap graph (always up-to-date, atomic stores)
|
||||
edges, \* edges[obj1][obj2] = TRUE if obj1.field points to obj2
|
||||
\* Stack roots per thread
|
||||
roots, \* roots[thread][obj] = TRUE if thread has local var pointing to obj
|
||||
\* Reference counts (stored in object header)
|
||||
rc, \* rc[obj] = reference count (logical, after merge)
|
||||
\* Color markers (stored in object header, bits 0-2)
|
||||
color, \* color[obj] \in {colBlack, colGray, colWhite}
|
||||
\* Root tracking flags
|
||||
inRoots, \* inRoots[obj] = TRUE if obj is in roots array
|
||||
\* Striped increment queues
|
||||
toIncLen, \* toIncLen[stripe] = current length of increment queue
|
||||
toInc, \* toInc[stripe][i] = object to increment
|
||||
\* Striped decrement queues
|
||||
toDecLen, \* toDecLen[stripe] = current length of decrement queue
|
||||
toDec, \* toDec[stripe][i] = (object, type) pair to decrement
|
||||
\* Per-stripe locks
|
||||
lockInc, \* lockInc[stripe] = thread holding increment lock (or NULL)
|
||||
lockDec, \* lockDec[stripe] = thread holding decrement lock (or NULL)
|
||||
\* Global lock (now the WRITE side of the RWLock)
|
||||
globalLock, \* thread holding write lock (or NULL)
|
||||
\* Merged roots array (used during collection)
|
||||
mergedRoots, \* sequence of (object, type) pairs
|
||||
\* Collection state
|
||||
collecting, \* TRUE if collection is in progress
|
||||
gcEnv, \* GC environment: {touched, edges, rcSum, toFree, ...}
|
||||
\* Pending operations (for modeling atomicity)
|
||||
pendingWrites, \* set of pending write barrier operations
|
||||
\* --- Seq payload race modeling ---
|
||||
\* Seq payloads: models the heap-allocated data arrays of seq[T] fields
|
||||
seqData, \* [Objects -> SeqPayloads \cup {NULL}] -- current payload for obj's seq
|
||||
payloadAlive, \* [SeqPayloads -> BOOLEAN] -- is this payload's memory valid?
|
||||
\* RWLock read side: set of threads holding the read lock.
|
||||
\* Seq mutations (assign, add, setLen, etc.) acquire the read lock.
|
||||
\* The collector (write lock holder) gets exclusive access.
|
||||
rwLockReaders, \* SUBSET Threads -- threads currently holding the read lock
|
||||
\* Collector's in-progress seq trace: the payload pointer read during tracing.
|
||||
\* Between reading the pointer and accessing the data, the payload could be freed.
|
||||
collectorPayload \* SeqPayloads \cup {NULL} -- payload being traced by collector
|
||||
|
||||
\* Convenience tuple for seq-related variables (used in UNCHANGED clauses)
|
||||
seqVars == <<seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
\* Type invariants
|
||||
TypeOK ==
|
||||
/\ edges \in [Objects -> [Objects -> BOOLEAN]]
|
||||
/\ roots \in [Threads -> [Objects -> BOOLEAN]]
|
||||
/\ rc \in [Objects -> Int]
|
||||
/\ color \in [Objects -> {colBlack, colGray, colWhite}]
|
||||
/\ inRoots \in [Objects -> BOOLEAN]
|
||||
/\ toIncLen \in [0..(NumStripes-1) -> 0..QueueSize]
|
||||
/\ toInc \in [0..(NumStripes-1) -> Seq(Objects)]
|
||||
/\ toDecLen \in [0..(NumStripes-1) -> 0..QueueSize]
|
||||
/\ toDec \in [0..(NumStripes-1) -> Seq([obj: Objects, desc: ObjTypes])]
|
||||
/\ lockInc \in [0..(NumStripes-1) -> Threads \cup {NULL}]
|
||||
/\ lockDec \in [0..(NumStripes-1) -> Threads \cup {NULL}]
|
||||
/\ globalLock \in Threads \cup {NULL}
|
||||
/\ mergedRoots \in Seq([obj: Objects, desc: ObjTypes])
|
||||
/\ collecting \in BOOLEAN
|
||||
/\ pendingWrites \in SUBSET ([thread: Threads, dest: Objects, old: Objects \cup {NULL}, src: Objects \cup {NULL}, phase: {"store", "inc", "dec"}])
|
||||
\* Seq payload types
|
||||
/\ seqData \in [Objects -> SeqPayloads \cup {NULL}]
|
||||
/\ payloadAlive \in [SeqPayloads -> BOOLEAN]
|
||||
/\ rwLockReaders \in SUBSET Threads
|
||||
/\ collectorPayload \in SeqPayloads \cup {NULL}
|
||||
|
||||
\* Helper: internal reference count (heap-to-heap edges)
|
||||
InternalRC(obj) ==
|
||||
Cardinality({src \in Objects : edges[src][obj]})
|
||||
|
||||
\* Helper: external reference count (stack roots)
|
||||
ExternalRC(obj) ==
|
||||
Cardinality({t \in Threads : roots[t][obj]})
|
||||
|
||||
\* Helper: logical reference count
|
||||
LogicalRC(obj) ==
|
||||
InternalRC(obj) + ExternalRC(obj)
|
||||
|
||||
\* Helper: get stripe index for thread
|
||||
\* Map threads to stripe indices deterministically
|
||||
\* Since threads are ModelValues, we use a simple deterministic mapping:
|
||||
\* Assign each thread to stripe 0 (for small models, this is fine)
|
||||
\* For larger models, TLC will handle the mapping deterministically
|
||||
GetStripe(thread) == 0
|
||||
|
||||
\* ============================================================================
|
||||
\* Write Barrier: nimAsgnYrc
|
||||
\* ============================================================================
|
||||
\* The write barrier does:
|
||||
\* 1. atomicStoreN(dest, src, ATOMIC_RELEASE) -- graph update is immediate
|
||||
\* 2. nimIncRefCyclic(src, true) -- buffer inc(src)
|
||||
\* 3. yrcDec(tmp, desc) -- buffer dec(old)
|
||||
\*
|
||||
\* Key barrier semantics:
|
||||
\* - ATOMIC_RELEASE on store ensures all prior writes are visible before the graph update
|
||||
\* - The graph update is immediately visible to all threads (including collector)
|
||||
\* - RC adjustments are buffered and only applied during merge
|
||||
|
||||
\* ============================================================================
|
||||
\* Phase 1: Atomic Store (Topology Update)
|
||||
\* ============================================================================
|
||||
\* The atomic store always happens first, updating the graph topology.
|
||||
\* This is independent of RC operations and never blocks.
|
||||
MutatorWriteAtomicStore(thread, destObj, destField, oldVal, newVal, desc) ==
|
||||
\* Atomic store with RELEASE barrier - updates graph topology immediately
|
||||
\* Clear ALL edges from destObj first (atomic store replaces old value completely),
|
||||
\* then set the new edge. This ensures destObj.field can only point to one object.
|
||||
/\ edges' = [edges EXCEPT ![destObj] = [x \in Objects |->
|
||||
IF x = newVal /\ newVal # NULL
|
||||
THEN TRUE
|
||||
ELSE FALSE]]
|
||||
/\ UNCHANGED <<roots, rc, color, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, mergedRoots, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
\* ============================================================================
|
||||
\* Phase 2: RC Buffering (if space available)
|
||||
\* ============================================================================
|
||||
\* Buffers increment/decrement if there's space. If overflow would happen,
|
||||
\* this action is disabled (blocked) until merge can happen.
|
||||
WriteBarrier(thread, destObj, destField, oldVal, newVal, desc) ==
|
||||
LET stripe == GetStripe(thread)
|
||||
IN
|
||||
\* Determine if overflow happens for increment or decrement
|
||||
/\ LET
|
||||
incOverflow == (newVal # NULL) /\ (toIncLen[stripe] >= QueueSize)
|
||||
decOverflow == (oldVal # NULL) /\ (toDecLen[stripe] >= QueueSize)
|
||||
IN
|
||||
\* Buffering: only enabled if no overflow (otherwise blocked until merge can happen)
|
||||
/\ ~incOverflow \* Precondition: increment buffer has space (blocks if full)
|
||||
/\ ~decOverflow \* Precondition: decrement buffer has space (blocks if full)
|
||||
/\ toIncLen' = IF newVal # NULL /\ toIncLen[stripe] < QueueSize
|
||||
THEN [toIncLen EXCEPT ![stripe] = toIncLen[stripe] + 1]
|
||||
ELSE toIncLen
|
||||
/\ toInc' = IF newVal # NULL /\ toIncLen[stripe] < QueueSize
|
||||
THEN [toInc EXCEPT ![stripe] = Append(toInc[stripe], newVal)]
|
||||
ELSE toInc
|
||||
/\ toDecLen' = IF oldVal # NULL /\ toDecLen[stripe] < QueueSize
|
||||
THEN [toDecLen EXCEPT ![stripe] = toDecLen[stripe] + 1]
|
||||
ELSE toDecLen
|
||||
/\ toDec' = IF oldVal # NULL /\ toDecLen[stripe] < QueueSize
|
||||
THEN [toDec EXCEPT ![stripe] = Append(toDec[stripe], [obj |-> oldVal, desc |-> desc])]
|
||||
ELSE toDec
|
||||
/\ UNCHANGED <<edges, roots, rc, color, inRoots, mergedRoots, lockInc, lockDec, globalLock, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
\* ============================================================================
|
||||
\* Phase 3: Overflow Handling (separate actions that can block)
|
||||
\* ============================================================================
|
||||
|
||||
\* Handle increment overflow: merge increment buffers when lock is available
|
||||
\* This merges ALL increment buffers (for all stripes), not just the one that overflowed
|
||||
MutatorWriteMergeInc(thread) ==
|
||||
LET stripe == GetStripe(thread)
|
||||
IN
|
||||
/\ \E s \in 0..(NumStripes-1): toIncLen[s] >= QueueSize \* Some stripe has increment overflow
|
||||
/\ globalLock = NULL \* Lock must be available (blocks if held)
|
||||
/\ toIncLen' = [s \in 0..(NumStripes-1) |-> 0]
|
||||
/\ toInc' = [s \in 0..(NumStripes-1) |-> <<>>]
|
||||
/\ rc' = \* Compute RC from LogicalRC of current graph (increment buffers merged)
|
||||
\* The graph is already updated by atomic store, so we compute from current edges
|
||||
[x \in Objects |->
|
||||
LET internalRC == Cardinality({src \in Objects : edges[src][x]})
|
||||
externalRC == Cardinality({t \in Threads : roots[t][x]})
|
||||
IN internalRC + externalRC]
|
||||
/\ globalLock' = NULL \* Release lock after merge
|
||||
/\ UNCHANGED <<edges, roots, color, inRoots, toDecLen, toDec, lockInc, lockDec, mergedRoots, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
\* Handle decrement overflow: merge ALL buffers when lock is available
|
||||
\* This calls collectCycles() which merges both increment and decrement buffers
|
||||
\* We inline MergePendingRoots here. The entire withLock block is atomic:
|
||||
\* lock is acquired, merge happens, lock is released.
|
||||
MutatorWriteMergeDec(thread) ==
|
||||
LET stripe == GetStripe(thread)
|
||||
IN
|
||||
/\ \E s \in 0..(NumStripes-1): toDecLen[s] >= QueueSize \* Some stripe has decrement overflow
|
||||
/\ globalLock = NULL \* Lock must be available (blocks if held)
|
||||
/\ \* Merge all buffers (inlined MergePendingRoots logic)
|
||||
LET \* Compute new RC by merging all buffered increments and decrements
|
||||
\* For each object, count buffered increments and decrements
|
||||
bufferedInc == UNION {{toInc[s][i] : i \in 1..toIncLen[s]} : s \in 0..(NumStripes-1)}
|
||||
bufferedDec == UNION {{toDec[s][i].obj : i \in 1..toDecLen[s]} : s \in 0..(NumStripes-1)}
|
||||
\* Compute RC: current graph state (edges) + roots - buffered decrements + buffered increments
|
||||
\* Actually, we compute from LogicalRC of current graph (buffers are merged)
|
||||
newRC == [x \in Objects |->
|
||||
LET internalRC == Cardinality({src \in Objects : edges[src][x]})
|
||||
externalRC == Cardinality({t \in Threads : roots[t][x]})
|
||||
IN internalRC + externalRC]
|
||||
\* Collect objects from decrement buffers for mergedRoots
|
||||
newRootsSet == UNION {{toDec[s][i].obj : i \in 1..toDecLen[s]} : s \in 0..(NumStripes-1)}
|
||||
newRootsSeq == IF newRootsSet = {}
|
||||
THEN <<>>
|
||||
ELSE LET ordered == CHOOSE f \in [1..Cardinality(newRootsSet) -> newRootsSet] :
|
||||
\A i, j \in DOMAIN f : i # j => f[i] # f[j]
|
||||
IN [i \in 1..Cardinality(newRootsSet) |-> ordered[i]]
|
||||
IN
|
||||
/\ rc' = newRC
|
||||
/\ mergedRoots' = mergedRoots \o newRootsSeq
|
||||
/\ inRoots' = [x \in Objects |->
|
||||
IF newRootsSet = {}
|
||||
THEN inRoots[x]
|
||||
ELSE LET rootObjs == UNION {{mergedRoots'[i].obj : i \in DOMAIN mergedRoots'}}
|
||||
IN IF x \in rootObjs THEN TRUE ELSE inRoots[x]]
|
||||
/\ toIncLen' = [s \in 0..(NumStripes-1) |-> 0]
|
||||
/\ toInc' = [s \in 0..(NumStripes-1) |-> <<>>]
|
||||
/\ toDecLen' = [s \in 0..(NumStripes-1) |-> 0]
|
||||
/\ toDec' = [s \in 0..(NumStripes-1) |-> <<>>]
|
||||
/\ globalLock' = NULL \* Lock acquired, merge done, lock released (entire withLock block is atomic)
|
||||
/\ UNCHANGED <<edges, roots, color, lockInc, lockDec, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
\* ============================================================================
|
||||
\* Merge Operation: mergePendingRoots
|
||||
\* ============================================================================
|
||||
\* Drains all stripe buffers under global lock.
|
||||
\* Sequentially acquires each stripe's lockInc and lockDec to drain buffers.
|
||||
\* This reconciles buffered RC adjustments with the current graph state.
|
||||
\*
|
||||
\* Key invariant: After merge, mergedRC = logicalRC (current graph + buffered changes)
|
||||
|
||||
MergePendingRoots ==
|
||||
/\ globalLock # NULL
|
||||
/\ LET
|
||||
\* Count pending increments per object (across all stripes)
|
||||
pendingInc == [x \in Objects |->
|
||||
Cardinality(UNION {{i \in DOMAIN toInc[s] : toInc[s][i] = x} :
|
||||
s \in 0..(NumStripes-1)})]
|
||||
\* Count pending decrements per object (across all stripes)
|
||||
pendingDec == [x \in Objects |->
|
||||
Cardinality(UNION {{i \in DOMAIN toDec[s] : toDec[s][i].obj = x} :
|
||||
s \in 0..(NumStripes-1)})]
|
||||
\* After merge, RC should equal LogicalRC (current graph state)
|
||||
\* The buffered changes compensate for graph changes that already happened,
|
||||
\* so: mergedRC = currentRC + pendingInc - pendingDec = LogicalRC(current graph)
|
||||
\* But to ensure correctness, we compute directly from the current graph:
|
||||
newRC == [x \in Objects |->
|
||||
LogicalRC(x)] \* RC after merge equals logical RC of current graph
|
||||
\* Add decremented objects to roots if not already there (check inRootsFlag)
|
||||
\* Collect all new roots as a set, then convert to sequence
|
||||
\* Build set by iterating over all (stripe, index) pairs
|
||||
\* Use UNION with explicit per-stripe sets (avoiding function enumeration issues)
|
||||
newRootsSet == UNION {UNION {IF inRoots[toDec[s][i].obj] = FALSE
|
||||
THEN {[obj |-> toDec[s][i].obj, desc |-> toDec[s][i].desc]}
|
||||
ELSE {} : i \in DOMAIN toDec[s]} : s \in 0..(NumStripes-1)}
|
||||
newRootsSeq == IF newRootsSet = {}
|
||||
THEN <<>>
|
||||
ELSE LET ordered == CHOOSE f \in [1..Cardinality(newRootsSet) -> newRootsSet] :
|
||||
\A i, j \in DOMAIN f : i # j => f[i] # f[j]
|
||||
IN [i \in 1..Cardinality(newRootsSet) |-> ordered[i]]
|
||||
IN
|
||||
/\ rc' = newRC
|
||||
/\ mergedRoots' = mergedRoots \o newRootsSeq \* Append new roots to sequence
|
||||
/\ \* Update inRoots: mark objects in mergedRoots' as being in roots
|
||||
\* Use explicit iteration to avoid enumeration issues
|
||||
inRoots' = [x \in Objects |->
|
||||
IF mergedRoots' = <<>>
|
||||
THEN inRoots[x]
|
||||
ELSE LET rootObjs == UNION {{mergedRoots'[i].obj : i \in DOMAIN mergedRoots'}}
|
||||
IN IF x \in rootObjs THEN TRUE ELSE inRoots[x]]
|
||||
/\ toIncLen' = [s \in 0..(NumStripes-1) |-> 0]
|
||||
/\ toInc' = [s \in 0..(NumStripes-1) |-> <<>>]
|
||||
/\ toDecLen' = [s \in 0..(NumStripes-1) |-> 0]
|
||||
/\ toDec' = [s \in 0..(NumStripes-1) |-> <<>>]
|
||||
/\ UNCHANGED <<edges, roots, color, lockInc, lockDec, globalLock, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
\* ============================================================================
|
||||
\* Trial Deletion: markGray
|
||||
\* ============================================================================
|
||||
\* Subtracts internal (heap-to-heap) edges from reference counts.
|
||||
\* This isolates external references (stack roots).
|
||||
\*
|
||||
\* Algorithm:
|
||||
\* 1. Mark obj gray
|
||||
\* 2. Trace obj's fields (via traceImpl)
|
||||
\* 3. For each child c: decrement c.rc (subtract internal edge)
|
||||
\* 4. Recursively markGray all children
|
||||
\*
|
||||
\* After markGray: trialRC(obj) = mergedRC(obj) - internalRefCount(obj)
|
||||
\* = externalRefCount(obj) (if merge was correct)
|
||||
|
||||
MarkGray(obj, desc) ==
|
||||
/\ globalLock # NULL
|
||||
/\ collecting = TRUE
|
||||
/\ color[obj] # colGray
|
||||
/\ \* Compute transitive closure of all objects reachable from obj
|
||||
\* This models the recursive traversal in the actual implementation
|
||||
LET children == {c \in Objects : edges[obj][c]}
|
||||
\* Compute all objects reachable from obj via heap edges
|
||||
\* This is the transitive closure starting from obj's direct children
|
||||
allReachable == {c \in Objects :
|
||||
\E path \in Seq(Objects):
|
||||
Len(path) > 0 /\
|
||||
path[1] \in children /\
|
||||
path[Len(path)] = c /\
|
||||
\A i \in 1..(Len(path)-1):
|
||||
edges[path[i]][path[i+1]]}
|
||||
\* All objects to mark gray: obj itself + all reachable descendants
|
||||
objectsToMarkGray == {obj} \cup allReachable
|
||||
\* For each reachable object, count internal edges pointing to it
|
||||
\* from within the subgraph (obj + allReachable)
|
||||
\* This is the number of times its RC should be decremented
|
||||
subgraph == {obj} \cup allReachable
|
||||
internalEdgeCount == [x \in Objects |->
|
||||
IF x \in allReachable
|
||||
THEN Cardinality({y \in subgraph : edges[y][x]})
|
||||
ELSE 0]
|
||||
IN
|
||||
/\ \* Mark obj and all reachable objects gray
|
||||
color' = [x \in Objects |->
|
||||
IF x \in objectsToMarkGray THEN colGray ELSE color[x]]
|
||||
/\ \* Subtract internal edges: for each reachable object, decrement its RC
|
||||
\* by the number of internal edges pointing to it from within the subgraph.
|
||||
\* This matches the Nim implementation which decrements once per edge traversed.
|
||||
\* Note: obj's RC is not decremented here (it has no parent in this subgraph).
|
||||
\* For roots, the RC includes external refs which survive trial deletion.
|
||||
rc' = [x \in Objects |->
|
||||
IF x \in allReachable THEN rc[x] - internalEdgeCount[x] ELSE rc[x]]
|
||||
/\ UNCHANGED <<edges, roots, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, mergedRoots, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
\* ============================================================================
|
||||
\* Scan Phase
|
||||
\* ============================================================================
|
||||
\* Objects with RC >= 0 after trial deletion are rescued (scanBlack).
|
||||
\* Objects with RC < 0 remain white (part of closed cycle).
|
||||
\*
|
||||
\* Key insight: scanBlack follows the *current* physical edges (which may have
|
||||
\* changed since merge due to concurrent writes). This ensures objects written
|
||||
\* during collection are still rescued.
|
||||
\*
|
||||
\* Algorithm:
|
||||
\* IF rc[obj] >= 0:
|
||||
\* scanBlack(obj): mark black, restore RC, trace and rescue all children
|
||||
\* ELSE:
|
||||
\* mark white (closed cycle with zero external refs)
|
||||
|
||||
Scan(obj, desc) ==
|
||||
/\ globalLock # NULL
|
||||
/\ collecting = TRUE
|
||||
/\ color[obj] = colGray
|
||||
/\ IF rc[obj] >= 0
|
||||
THEN \* scanBlack: rescue obj and all reachable objects
|
||||
\* This follows the current physical graph (atomic stores are visible)
|
||||
\* Restore RC for all reachable objects by incrementing by the number of
|
||||
\* internal edges pointing to each (matching what markGray subtracted)
|
||||
LET children == {c \in Objects : edges[obj][c]}
|
||||
allReachable == {c \in Objects :
|
||||
\E path \in Seq(Objects):
|
||||
Len(path) > 0 /\
|
||||
path[1] \in children /\
|
||||
path[Len(path)] = c /\
|
||||
\A i \in 1..(Len(path)-1):
|
||||
edges[path[i]][path[i+1]]}
|
||||
objectsToMarkBlack == {obj} \cup allReachable
|
||||
\* For each reachable object, count internal edges pointing to it
|
||||
\* from within the subgraph (obj + allReachable)
|
||||
\* This is the number of times its RC should be incremented (restored)
|
||||
subgraph == {obj} \cup allReachable
|
||||
internalEdgeCount == [x \in Objects |->
|
||||
IF x \in allReachable
|
||||
THEN Cardinality({y \in subgraph : edges[y][x]})
|
||||
ELSE 0]
|
||||
IN
|
||||
/\ \* Restore RC: increment by the number of internal edges pointing to each
|
||||
\* reachable object. This restores what markGray subtracted.
|
||||
\* Note: obj's RC is not incremented here (it wasn't decremented in markGray).
|
||||
\* The root's RC already reflects external refs which survived trial deletion.
|
||||
rc' = [x \in Objects |->
|
||||
IF x \in allReachable THEN rc[x] + internalEdgeCount[x] ELSE rc[x]]
|
||||
/\ \* Mark obj and all reachable objects black in one assignment
|
||||
color' = [x \in Objects |->
|
||||
IF x \in objectsToMarkBlack THEN colBlack ELSE color[x]]
|
||||
ELSE \* Mark white (part of closed cycle)
|
||||
/\ color' = [color EXCEPT ![obj] = colWhite]
|
||||
/\ UNCHANGED <<rc>>
|
||||
/\ UNCHANGED <<edges, roots, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, mergedRoots, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
\* ============================================================================
|
||||
\* Collection Phase: collectColor
|
||||
\* ============================================================================
|
||||
\* Frees objects of the target color that are not in roots.
|
||||
\*
|
||||
\* Safety: Only objects with color = targetColor AND ~inRoots[obj] are freed.
|
||||
\* These are closed cycles (zero external refs, not reachable from roots).
|
||||
|
||||
CollectColor(obj, desc, targetColor) ==
|
||||
/\ globalLock # NULL
|
||||
/\ collecting = TRUE
|
||||
/\ color[obj] = targetColor
|
||||
/\ ~inRoots[obj]
|
||||
/\ \* Free obj: nullify all its outgoing edges (prevents use-after-free)
|
||||
\* In the actual implementation, this happens during trace() when freeing
|
||||
edges' = [edges EXCEPT ![obj] = [x \in Objects |->
|
||||
IF x = obj THEN FALSE ELSE edges[obj][x]]]
|
||||
/\ color' = [color EXCEPT ![obj] = colBlack] \* Mark as freed
|
||||
/\ UNCHANGED <<roots, rc, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, mergedRoots, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
\* ============================================================================
|
||||
\* Collection Cycle: collectCyclesBacon
|
||||
\* ============================================================================
|
||||
|
||||
StartCollection ==
|
||||
/\ globalLock # NULL
|
||||
/\ ~collecting
|
||||
/\ Len(mergedRoots) >= RootsThreshold
|
||||
/\ collecting' = TRUE
|
||||
/\ gcEnv' = [touched |-> 0, edges |-> 0, rcSum |-> 0, toFree |-> {}]
|
||||
/\ UNCHANGED <<edges, roots, rc, color, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, mergedRoots, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
EndCollection ==
|
||||
/\ globalLock # NULL
|
||||
/\ collecting = TRUE
|
||||
/\ \* Clear root flags
|
||||
inRoots' = [x \in Objects |->
|
||||
IF x \in {r.obj : r \in mergedRoots} THEN FALSE ELSE inRoots[x]]
|
||||
/\ mergedRoots' = <<>>
|
||||
/\ collecting' = FALSE
|
||||
/\ UNCHANGED <<edges, roots, rc, color, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
\* ============================================================================
|
||||
\* Mutator Actions
|
||||
\* ============================================================================
|
||||
|
||||
\* Mutator can write at any time (graph updates are lock-free)
|
||||
\* The ATOMIC_RELEASE barrier ensures proper ordering
|
||||
\* ASSUMPTION: Users synchronize pointer assignments with locks, so oldVal always
|
||||
\* matches the current graph state (as read before the atomic store).
|
||||
\* This prevents races at the user level - the GC itself is lock-free.
|
||||
MutatorWrite(thread, destObj, destField, oldVal, newVal, desc) ==
|
||||
\* ASSUMPTION: Users synchronize pointer assignments with locks, so oldVal always matches
|
||||
\* the value read before the atomic store. This prevents races at the user level.
|
||||
\* The precondition is enforced in the Next relation.
|
||||
\* Phase 1: Atomic store (topology update) - ALWAYS happens first
|
||||
/\ MutatorWriteAtomicStore(thread, destObj, destField, oldVal, newVal, desc)
|
||||
\* Phase 2: RC buffering - happens if no overflow, otherwise overflow is handled separately
|
||||
\* Note: In reality, if overflow happens, the thread blocks waiting for lock.
|
||||
\* We model this as: atomic store happens, buffering is deferred (handled by merge actions).
|
||||
/\ LET stripe == GetStripe(thread)
|
||||
incOverflow == (newVal # NULL) /\ (toIncLen[stripe] >= QueueSize)
|
||||
decOverflow == (oldVal # NULL) /\ (toDecLen[stripe] >= QueueSize)
|
||||
IN
|
||||
IF incOverflow \/ decOverflow
|
||||
THEN \* Overflow: atomic store happened, but buffering is deferred
|
||||
\* Buffers stay full, merge will happen when lock is available (via MutatorWriteMergeInc/Dec)
|
||||
/\ UNCHANGED <<roots, rc, color, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, mergedRoots, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
ELSE \* No overflow: buffer normally
|
||||
/\ WriteBarrier(thread, destObj, destField, oldVal, newVal, desc)
|
||||
/\ UNCHANGED <<roots, collecting, pendingWrites>>
|
||||
|
||||
\* Stack root assignment: immediate RC increment (not buffered)
|
||||
\* When assigning val to a root variable named obj, we set roots[thread][val] = TRUE
|
||||
\* to indicate that thread has a stack reference to val
|
||||
\* Semantics: obj is root variable name, val is the object being assigned
|
||||
\* When val=NULL, obj was the old root value, so we decrement rc[obj]
|
||||
MutatorRootAssign(thread, obj, val) ==
|
||||
/\ IF val # NULL
|
||||
THEN /\ roots' = [roots EXCEPT ![thread][val] = TRUE]
|
||||
/\ rc' = [rc EXCEPT ![val] = IF roots[thread][val] THEN @ ELSE @ + 1] \* Increment only if not already a root
|
||||
ELSE /\ roots' = [roots EXCEPT ![thread][obj] = FALSE] \* Clear root when assigning NULL
|
||||
/\ rc' = [rc EXCEPT ![obj] = IF roots[thread][obj] THEN @ - 1 ELSE @] \* Decrement old root value
|
||||
/\ edges' = edges
|
||||
/\ color' = color
|
||||
/\ inRoots' = inRoots
|
||||
/\ toIncLen' = toIncLen
|
||||
/\ toInc' = toInc
|
||||
/\ toDecLen' = toDecLen
|
||||
/\ toDec' = toDec
|
||||
/\ lockInc' = lockInc
|
||||
/\ lockDec' = lockDec
|
||||
/\ globalLock' = globalLock
|
||||
/\ mergedRoots' = mergedRoots
|
||||
/\ collecting' = collecting
|
||||
/\ gcEnv' = gcEnv
|
||||
/\ pendingWrites' = pendingWrites
|
||||
/\ UNCHANGED seqVars
|
||||
|
||||
\* ============================================================================
|
||||
\* Collector Actions
|
||||
\* ============================================================================
|
||||
|
||||
\* Collector acquires write lock (global lock) for entire collection cycle.
|
||||
\* RWLock semantics: writer can only acquire when no readers hold the read lock.
|
||||
CollectorAcquireLock(thread) ==
|
||||
/\ globalLock = NULL
|
||||
/\ rwLockReaders = {} \* RWLock: no readers allowed when acquiring write lock
|
||||
/\ globalLock' = thread
|
||||
/\ UNCHANGED <<edges, roots, rc, color, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, mergedRoots, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
CollectorMerge ==
|
||||
/\ globalLock # NULL
|
||||
/\ MergePendingRoots
|
||||
|
||||
CollectorStart ==
|
||||
/\ globalLock # NULL
|
||||
/\ StartCollection
|
||||
|
||||
\* Mark all roots gray (trial deletion phase)
|
||||
CollectorMarkGray ==
|
||||
/\ globalLock # NULL
|
||||
/\ collecting = TRUE
|
||||
/\ \E rootIdx \in DOMAIN mergedRoots:
|
||||
LET root == mergedRoots[rootIdx]
|
||||
IN MarkGray(root.obj, root.desc)
|
||||
|
||||
\* Scan all roots (rescue phase)
|
||||
CollectorScan ==
|
||||
/\ globalLock # NULL
|
||||
/\ collecting = TRUE
|
||||
/\ \E rootIdx \in DOMAIN mergedRoots:
|
||||
LET root == mergedRoots[rootIdx]
|
||||
IN Scan(root.obj, root.desc)
|
||||
|
||||
\* Collect white/gray objects (free phase)
|
||||
CollectorCollect ==
|
||||
/\ globalLock # NULL
|
||||
/\ collecting = TRUE
|
||||
/\ \E rootIdx \in DOMAIN mergedRoots, targetColor \in {colGray, colWhite}:
|
||||
LET root == mergedRoots[rootIdx]
|
||||
IN CollectColor(root.obj, root.desc, targetColor)
|
||||
|
||||
CollectorEnd ==
|
||||
/\ globalLock # NULL
|
||||
/\ EndCollection
|
||||
|
||||
CollectorReleaseLock(thread) ==
|
||||
/\ globalLock = thread
|
||||
/\ globalLock' = NULL
|
||||
/\ UNCHANGED <<edges, roots, rc, color, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, mergedRoots, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
\* ============================================================================
|
||||
\* Seq Payload Actions (RWLock-protected)
|
||||
\* ============================================================================
|
||||
\* These actions model the race between the collector tracing seq payloads
|
||||
\* and mutators replacing/freeing seq payloads.
|
||||
\*
|
||||
\* The collector traces seq payloads in two steps:
|
||||
\* 1. CollectorStartTraceSeq: reads seqData[obj] (gets payload pointer)
|
||||
\* 2. CollectorFinishTraceSeq: accesses the payload data
|
||||
\* Between these steps, a mutator could free the payload (the race).
|
||||
\*
|
||||
\* The RWLock prevents this:
|
||||
\* - Collector holds write lock (globalLock) during tracing
|
||||
\* - MutatorSeqAssign requires read lock (rwLockReaders)
|
||||
\* - Read lock requires globalLock = NULL
|
||||
\* - Therefore MutatorSeqAssign is blocked during collection
|
||||
\*
|
||||
\* Note: This models the memory safety aspect of seq tracing.
|
||||
\* The cycle collection algorithm (MarkGray, Scan, etc.) operates on the
|
||||
\* logical edge graph. Seq payloads are a physical representation detail
|
||||
\* that affects memory safety but not GC correctness (which is already
|
||||
\* covered by the existing Safety property).
|
||||
|
||||
\* Mutator acquires read lock for seq mutation.
|
||||
\* RWLock semantics: read lock can be acquired when no writer holds the write lock.
|
||||
\* Multiple readers can hold the read lock simultaneously.
|
||||
MutatorAcquireSeqLock(thread) ==
|
||||
/\ globalLock = NULL \* RWLock: no writer allowed when acquiring read lock
|
||||
/\ thread \notin rwLockReaders
|
||||
/\ rwLockReaders' = rwLockReaders \cup {thread}
|
||||
/\ UNCHANGED <<edges, roots, rc, color, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, mergedRoots, collecting, gcEnv, pendingWrites, seqData, payloadAlive, collectorPayload>>
|
||||
|
||||
\* Mutator releases read lock after seq mutation completes.
|
||||
MutatorReleaseSeqLock(thread) ==
|
||||
/\ thread \in rwLockReaders
|
||||
/\ rwLockReaders' = rwLockReaders \ {thread}
|
||||
/\ UNCHANGED <<edges, roots, rc, color, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, mergedRoots, collecting, gcEnv, pendingWrites, seqData, payloadAlive, collectorPayload>>
|
||||
|
||||
\* Mutator replaces a seq field's payload (e.g., r.list = newSeq).
|
||||
\* This frees the old payload and installs a new one.
|
||||
\* Requires the read lock (RWLock protection against concurrent collection).
|
||||
\*
|
||||
\* In the real implementation, this is a value-type assignment (=sink/=copy)
|
||||
\* that frees the old data array and installs a new one. The old array is freed
|
||||
\* immediately, NOT deferred to the cycle collector.
|
||||
MutatorSeqAssign(thread, obj, newPayload) ==
|
||||
/\ thread \in rwLockReaders \* Must hold read lock
|
||||
/\ seqData[obj] # NULL \* Object has an existing seq payload
|
||||
/\ newPayload \in SeqPayloads
|
||||
/\ ~payloadAlive[newPayload] \* New payload is freshly allocated (not yet alive)
|
||||
/\ LET oldPayload == seqData[obj]
|
||||
IN
|
||||
/\ seqData' = [seqData EXCEPT ![obj] = newPayload]
|
||||
/\ payloadAlive' = [payloadAlive EXCEPT ![oldPayload] = FALSE,
|
||||
![newPayload] = TRUE]
|
||||
\* Note: In a complete model, this would also update edges[obj] to reflect
|
||||
\* the new seq elements and buffer RC changes (inc new elements, dec old elements).
|
||||
\* We omit this here to focus on the memory safety property (payload lifetime).
|
||||
/\ UNCHANGED <<edges, roots, rc, color, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, mergedRoots, collecting, gcEnv, pendingWrites, rwLockReaders, collectorPayload>>
|
||||
|
||||
\* Collector begins tracing an object's seq field.
|
||||
\* Reads the seqData pointer and stores it in collectorPayload.
|
||||
\* This is the first step of a two-step trace operation.
|
||||
\* The collector must hold the write lock (globalLock).
|
||||
CollectorStartTraceSeq(obj) ==
|
||||
/\ globalLock # NULL \* Collector holds write lock
|
||||
/\ collecting = TRUE \* In collection phase
|
||||
/\ seqData[obj] # NULL \* Object has a seq field
|
||||
/\ collectorPayload = NULL \* Not already mid-trace
|
||||
/\ collectorPayload' = seqData[obj]
|
||||
/\ UNCHANGED <<edges, roots, rc, color, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, mergedRoots, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders>>
|
||||
|
||||
\* Collector finishes tracing an object's seq field.
|
||||
\* Accesses the payload data via collectorPayload.
|
||||
\* The payload MUST still be alive (this is checked by SeqPayloadSafety).
|
||||
\* After accessing the payload, clears collectorPayload.
|
||||
CollectorFinishTraceSeq ==
|
||||
/\ globalLock # NULL \* Collector holds write lock
|
||||
/\ collecting = TRUE \* In collection phase
|
||||
/\ collectorPayload # NULL \* Mid-trace on a payload
|
||||
\* The actual work: read payloadEdges[collectorPayload] to discover children.
|
||||
\* We don't model the trace results here; the safety property ensures
|
||||
\* the read is valid (payload is alive).
|
||||
/\ collectorPayload' = NULL
|
||||
/\ UNCHANGED <<edges, roots, rc, color, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, mergedRoots, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders>>
|
||||
|
||||
\* ============================================================================
|
||||
\* Next State Relation
|
||||
\* ============================================================================
|
||||
|
||||
Next ==
|
||||
\/ \E thread \in Threads:
|
||||
\E destObj \in Objects, oldVal, newVal \in Objects \cup {NULL}, desc \in ObjTypes:
|
||||
\* Precondition: oldVal must match current graph state (user-level synchronization)
|
||||
\* ASSUMPTION: Users synchronize pointer assignments with locks, so oldVal always matches
|
||||
\* the value read before the atomic store. This prevents races at the user level.
|
||||
/\ LET oldValMatches == CASE oldVal = NULL -> TRUE
|
||||
[] oldVal \in Objects -> edges[destObj][oldVal]
|
||||
[] OTHER -> FALSE
|
||||
IN oldValMatches
|
||||
/\ MutatorWrite(thread, destObj, "field", oldVal, newVal, desc)
|
||||
\/ \E thread \in Threads:
|
||||
\* Handle increment overflow: merge increment buffers when lock becomes available
|
||||
MutatorWriteMergeInc(thread)
|
||||
\/ \E thread \in Threads:
|
||||
\* Handle decrement overflow: merge all buffers when lock becomes available
|
||||
MutatorWriteMergeDec(thread)
|
||||
\/ \E thread \in Threads:
|
||||
\E obj, val \in Objects \cup {NULL}:
|
||||
MutatorRootAssign(thread, obj, val)
|
||||
\/ \E thread \in Threads:
|
||||
CollectorAcquireLock(thread)
|
||||
\/ CollectorMerge
|
||||
\/ CollectorStart
|
||||
\/ CollectorMarkGray
|
||||
\/ CollectorScan
|
||||
\/ CollectorCollect
|
||||
\/ CollectorEnd
|
||||
\/ \E thread \in Threads:
|
||||
CollectorReleaseLock(thread)
|
||||
\* --- Seq payload actions ---
|
||||
\/ \E thread \in Threads:
|
||||
MutatorAcquireSeqLock(thread)
|
||||
\/ \E thread \in Threads:
|
||||
MutatorReleaseSeqLock(thread)
|
||||
\/ \E thread \in Threads, obj \in Objects, p \in SeqPayloads:
|
||||
MutatorSeqAssign(thread, obj, p)
|
||||
\/ \E obj \in Objects:
|
||||
CollectorStartTraceSeq(obj)
|
||||
\/ CollectorFinishTraceSeq
|
||||
|
||||
\* ============================================================================
|
||||
\* Initial State
|
||||
\* ============================================================================
|
||||
|
||||
Init ==
|
||||
/\ edges = [x \in Objects |->
|
||||
[y \in Objects |->
|
||||
IF x = y THEN FALSE ELSE FALSE]] \* Empty graph initially
|
||||
/\ roots = [t \in Threads |->
|
||||
[x \in Objects |->
|
||||
FALSE]] \* No stack roots initially
|
||||
/\ rc = [x \in Objects |->
|
||||
0] \* Zero reference counts
|
||||
/\ color = [x \in Objects |->
|
||||
colBlack] \* All objects black initially
|
||||
/\ inRoots = [x \in Objects |->
|
||||
FALSE] \* No objects in roots array
|
||||
/\ toIncLen = [s \in 0..(NumStripes-1) |->
|
||||
0]
|
||||
/\ toInc = [s \in 0..(NumStripes-1) |->
|
||||
<<>>]
|
||||
/\ toDecLen = [s \in 0..(NumStripes-1) |->
|
||||
0]
|
||||
/\ toDec = [s \in 0..(NumStripes-1) |->
|
||||
<<>>]
|
||||
/\ lockInc = [s \in 0..(NumStripes-1) |->
|
||||
NULL]
|
||||
/\ lockDec = [s \in 0..(NumStripes-1) |->
|
||||
NULL]
|
||||
/\ globalLock = NULL
|
||||
/\ mergedRoots = <<>>
|
||||
/\ collecting = FALSE
|
||||
/\ gcEnv = [touched |-> 0, edges |-> 0, rcSum |-> 0, toFree |-> {}]
|
||||
/\ pendingWrites = {}
|
||||
\* Seq payload initial state
|
||||
/\ seqData = [x \in Objects |-> NULL] \* No seq fields initially
|
||||
/\ payloadAlive = [p \in SeqPayloads |-> FALSE] \* No payloads alive initially
|
||||
/\ rwLockReaders = {} \* No threads hold read lock
|
||||
/\ collectorPayload = NULL \* Collector not mid-trace
|
||||
/\ TypeOK
|
||||
|
||||
\* ============================================================================
|
||||
\* Safety Properties
|
||||
\* ============================================================================
|
||||
|
||||
\* Safety: Objects are only freed if they are unreachable from any thread's stack
|
||||
\*
|
||||
\* An object is reachable if:
|
||||
\* - It is a direct stack root (roots[t][obj] = TRUE), OR
|
||||
\* - There exists a path from a stack root to obj via heap edges
|
||||
\*
|
||||
\* Safety guarantee: If an object is reachable, then:
|
||||
\* - It is not white (not marked for collection), OR
|
||||
\* - It is in roots array (protected from collection), OR
|
||||
\* - It is reachable from an object that will be rescued by scanBlack
|
||||
\*
|
||||
\* More precisely: Only closed cycles (zero external refs, unreachable) are freed.
|
||||
|
||||
\* Helper: Compute next set of reachable objects (one step of transitive closure)
|
||||
ReachableStep(current) ==
|
||||
current \cup UNION {{y \in Objects : edges[x][y]} : x \in current}
|
||||
|
||||
\* Compute the set of all reachable objects using bounded iteration
|
||||
\* Since Objects is finite, we iterate at most Cardinality(Objects) times
|
||||
\* This computes the transitive closure of edges starting from stack roots
|
||||
\* We unroll the iteration explicitly to avoid recursion issues with TLC
|
||||
ReachableSet ==
|
||||
LET StackRoots == {x \in Objects : \E t \in Threads : roots[t][x]}
|
||||
Step1 == ReachableStep(StackRoots)
|
||||
Step2 == ReachableStep(Step1)
|
||||
Step3 == ReachableStep(Step2)
|
||||
Step4 == ReachableStep(Step3)
|
||||
\* Add more steps if needed for larger object sets
|
||||
\* For small models (2 objects), 4 steps is sufficient
|
||||
IN Step4
|
||||
|
||||
\* Check if an object is reachable
|
||||
Reachable(obj) == obj \in ReachableSet
|
||||
|
||||
\* Helper: Check if there's a path from 'from' to 'to'
|
||||
\* For small object sets, we check all possible paths by checking
|
||||
\* all combinations of intermediate objects
|
||||
\* Path of length 0: from = to
|
||||
\* Path of length 1: edges[from][to]
|
||||
\* Path of length 2: \E i1: edges[from][i1] /\ edges[i1][to]
|
||||
\* Path of length 3: \E i1, i2: edges[from][i1] /\ edges[i1][i2] /\ edges[i2][to]
|
||||
\* etc. up to Cardinality(Objects)
|
||||
HasPath(from, to) ==
|
||||
\/ from = to
|
||||
\/ edges[from][to]
|
||||
\/ \E i1 \in Objects:
|
||||
edges[from][i1] /\ (edges[i1][to] \/ \E i2 \in Objects:
|
||||
edges[i1][i2] /\ (edges[i2][to] \/ \E i3 \in Objects:
|
||||
edges[i2][i3] /\ edges[i3][to]))
|
||||
|
||||
\* Helper: Compute set of objects reachable from a given starting object
|
||||
\* Uses the same iterative approach as ReachableSet
|
||||
ReachableFrom(start) ==
|
||||
LET Step1 == ReachableStep({start})
|
||||
Step2 == ReachableStep(Step1)
|
||||
Step3 == ReachableStep(Step2)
|
||||
Step4 == ReachableStep(Step3)
|
||||
IN Step4
|
||||
|
||||
\* Safety: Reachable objects are never freed (remain white without being collected)
|
||||
\* A reachable object is safe if:
|
||||
\* - It's not white (not marked for collection), OR
|
||||
\* - It's in roots array (protected from collection), OR
|
||||
\* - There exists a black object in ReachableSet such that obj is reachable from it
|
||||
\* (the black object will be rescued by scanBlack, which rescues all white objects
|
||||
\* reachable from black objects)
|
||||
Safety ==
|
||||
\A obj \in Objects:
|
||||
IF obj \in ReachableSet
|
||||
THEN \/ color[obj] # colWhite \* Not marked for collection
|
||||
\/ inRoots[obj] \* Protected in roots array
|
||||
\/ \E blackObj \in ReachableSet:
|
||||
/\ color[blackObj] = colBlack \* Black object will be rescued by scanBlack
|
||||
/\ obj \in ReachableFrom(blackObj) \* obj is reachable from blackObj
|
||||
ELSE TRUE \* Unreachable objects may be freed (this is safe)
|
||||
|
||||
\* Invariant: Reference counts match logical counts after merge
|
||||
\* (This is maintained by MergePendingRoots)
|
||||
\* Note: Between merge and collection, RC = logicalRC.
|
||||
\* During collection (after markGray), RC may be modified by trial deletion.
|
||||
\* RC may be inconsistent when:
|
||||
\* - globalLock = NULL (buffered changes pending)
|
||||
\* - globalLock # NULL but merge hasn't happened yet (buffers still have pending changes)
|
||||
\* RC must equal LogicalRC when:
|
||||
\* - After merge (buffers are empty) and before collection starts
|
||||
RCInvariant ==
|
||||
IF globalLock = NULL
|
||||
THEN TRUE \* Not in collection, RC may be inconsistent (buffered changes pending)
|
||||
ELSE IF collecting = FALSE /\ \A s \in 0..(NumStripes-1): toIncLen[s] = 0 /\ toDecLen[s] = 0
|
||||
THEN \A obj \in Objects: rc[obj] = LogicalRC(obj) \* After merge, buffers empty, RC = logical RC
|
||||
ELSE TRUE \* During collection or before merge, RC may differ from logicalRC
|
||||
|
||||
\* Invariant: Only closed cycles are collected
|
||||
\* (Objects with external refs are rescued by scanBlack)
|
||||
CycleInvariant ==
|
||||
\A obj \in Objects:
|
||||
IF color[obj] = colWhite /\ ~inRoots[obj]
|
||||
THEN ExternalRC(obj) = 0
|
||||
ELSE TRUE
|
||||
|
||||
\* ============================================================================
|
||||
\* Seq Payload Safety
|
||||
\* ============================================================================
|
||||
\* Memory safety: The collector never accesses a freed seq payload.
|
||||
\*
|
||||
\* collectorPayload holds the payload pointer the collector read during
|
||||
\* CollectorStartTraceSeq. Between that action and CollectorFinishTraceSeq,
|
||||
\* the collector will dereference this pointer to read the seq's elements.
|
||||
\* If the payload has been freed in between, this is a use-after-free.
|
||||
\*
|
||||
\* The RWLock prevents this:
|
||||
\* - collectorPayload is only set when globalLock # NULL (write lock held)
|
||||
\* - MutatorSeqAssign (which frees payloads) requires rwLockReaders membership
|
||||
\* - MutatorAcquireSeqLock requires globalLock = NULL (no writer)
|
||||
\* - Therefore: while collectorPayload # NULL, no MutatorSeqAssign can execute
|
||||
\* - Therefore: payloadAlive[collectorPayload] remains TRUE
|
||||
\*
|
||||
\* Without the RWLock (if MutatorSeqAssign didn't require the read lock),
|
||||
\* the following interleaving would violate this property:
|
||||
\* 1. Collector acquires write lock
|
||||
\* 2. CollectorStartTraceSeq(obj) -- collectorPayload = P
|
||||
\* 3. MutatorSeqAssign(thread, obj, Q) -- frees P, payloadAlive[P] = FALSE
|
||||
\* 4. SeqPayloadSafety VIOLATED: collectorPayload = P but payloadAlive[P] = FALSE
|
||||
|
||||
SeqPayloadSafety ==
|
||||
collectorPayload # NULL => payloadAlive[collectorPayload]
|
||||
|
||||
\* ============================================================================
|
||||
\* RWLock Invariant
|
||||
\* ============================================================================
|
||||
\* The read-write lock ensures mutual exclusion between the collector (writer)
|
||||
\* and seq mutations (readers). The writer and readers are never active at
|
||||
\* the same time.
|
||||
|
||||
RWLockInvariant ==
|
||||
globalLock # NULL => rwLockReaders = {}
|
||||
|
||||
\* ============================================================================
|
||||
\* Specification
|
||||
\* ============================================================================
|
||||
|
||||
Spec == Init /\ [][Next]_<<edges, roots, rc, color, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, mergedRoots, collecting, gcEnv, pendingWrites, seqData, payloadAlive, rwLockReaders, collectorPayload>>
|
||||
|
||||
THEOREM Spec => []Safety
|
||||
THEOREM Spec => []RCInvariant
|
||||
THEOREM Spec => []CycleInvariant
|
||||
THEOREM Spec => []SeqPayloadSafety
|
||||
THEOREM Spec => []RWLockInvariant
|
||||
|
||||
====
|
||||
@@ -1,6 +1,5 @@
|
||||
discard """
|
||||
ccodeCheck: "\\i @'NIM_ALIGN(128) NI mylocal1' .*"
|
||||
matrix: "--mm:refc -d:useGcAssert -d:useSysAssert; --mm:orc"
|
||||
targets: "c cpp"
|
||||
output: "align ok"
|
||||
"""
|
||||
@@ -68,103 +67,3 @@ block: # bug #22419
|
||||
|
||||
f()()
|
||||
|
||||
|
||||
type Xxx = object
|
||||
v {.align: 128.}: byte
|
||||
|
||||
type Yyy = object
|
||||
v: byte
|
||||
v2: Xxx
|
||||
|
||||
for i in 0..<3:
|
||||
let x = new Yyy
|
||||
# echo "addr v2.v:", cast[uint](addr x.v2.v)
|
||||
doAssert cast[uint](addr x.v2.v) mod 128 == 0
|
||||
|
||||
let m = new Yyy
|
||||
m.v2.v = 42
|
||||
doAssert m.v2.v == 42
|
||||
m.v = 7
|
||||
doAssert m.v == 7
|
||||
|
||||
|
||||
type
|
||||
MyType16 = object
|
||||
a {.align(16).}: int
|
||||
|
||||
|
||||
var x: array[10, ref MyType16]
|
||||
for q in 0..500:
|
||||
for i in 0..<x.len:
|
||||
new x[i]
|
||||
x[i].a = q
|
||||
doAssert(cast[int](x[i]) mod alignof(MyType16) == 0)
|
||||
|
||||
type
|
||||
MyType32 = object
|
||||
a{.align(32).}: int
|
||||
|
||||
var y: array[10, ref MyType32]
|
||||
for q in 0..500:
|
||||
for i in 0..<y.len:
|
||||
new y[i]
|
||||
y[i].a = q
|
||||
doAssert(cast[int](y[i]) mod alignof(MyType32) == 0)
|
||||
|
||||
# Additional tests: allocate custom aligned objects using `new`
|
||||
type
|
||||
MyType64 = object
|
||||
a{.align(64).}: int
|
||||
|
||||
var z: array[10, ref MyType64]
|
||||
for q in 0..500:
|
||||
for i in 0..<z.len:
|
||||
new z[i]
|
||||
z[i].a = q
|
||||
doAssert(cast[int](z[i]) mod alignof(MyType64) == 0)
|
||||
|
||||
type
|
||||
MyType128 = object
|
||||
a{.align(128).}: int
|
||||
|
||||
var w: array[10, ref MyType128]
|
||||
for q in 0..500:
|
||||
for i in 0..<w.len:
|
||||
new w[i]
|
||||
w[i].a = q
|
||||
doAssert(cast[int](w[i]) mod alignof(MyType128) == 0)
|
||||
|
||||
# Nested aligned-object tests
|
||||
type
|
||||
Inner128 = object
|
||||
v {.align(128).}: byte
|
||||
|
||||
OuterWithInner = object
|
||||
prefix: int
|
||||
inner: Inner128
|
||||
|
||||
var outerArr: array[8, ref OuterWithInner]
|
||||
for q in 0..200:
|
||||
for i in 0..<outerArr.len:
|
||||
new outerArr[i]
|
||||
# write to inner to ensure it's allocated
|
||||
outerArr[i].inner.v = cast[byte](q and 0xFF)
|
||||
doAssert(cast[uint](addr outerArr[i].inner) mod uint(alignof(Inner128)) == 0)
|
||||
|
||||
# Nested two-level alignment
|
||||
type
|
||||
DeepInner = object
|
||||
b {.align(128).}: int
|
||||
|
||||
Mid = object
|
||||
di: DeepInner
|
||||
|
||||
Top = object
|
||||
m: Mid
|
||||
|
||||
var topArr: array[4, ref Top]
|
||||
for q in 0..100:
|
||||
for i in 0..<topArr.len:
|
||||
new topArr[i]
|
||||
topArr[i].m.di.b = q
|
||||
doAssert(cast[uint](addr topArr[i].m.di) mod uint(alignof(DeepInner)) == 0)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
discard """
|
||||
matrix: "--mm:refc -d:useGcAssert -d:useSysAssert; --mm:orc"
|
||||
"""
|
||||
|
||||
block:
|
||||
type U = object
|
||||
d {.align: 16.}: int8
|
||||
var e: seq[ref U]
|
||||
for i in 0 ..< 10000: e.add(new U)
|
||||
doAssert getTotalMem() <= 1052672 * 2
|
||||
@@ -35,5 +35,4 @@ proc main() =
|
||||
|
||||
main()
|
||||
GC_fullCollect()
|
||||
when not defined(useMalloc):
|
||||
echo getOccupiedMem() < 10 * 1024 * 1024, " peak memory: ", getMaxMem() < 10 * 1024 * 1024
|
||||
echo getOccupiedMem() < 10 * 1024 * 1024, " peak memory: ", getMaxMem() < 10 * 1024 * 1024
|
||||
|
||||
@@ -605,17 +605,3 @@ block t18643:
|
||||
except IndexDefect:
|
||||
caught = true
|
||||
doAssert caught, "IndexDefect not caught!"
|
||||
|
||||
|
||||
# bug #25475
|
||||
block:
|
||||
type N = object
|
||||
b: seq[array[1'u, int]]
|
||||
doAssert N(b: @[[0]]) == N(b: @[[0]])
|
||||
|
||||
block:
|
||||
var x: array[5..6, int] = [0, 1]
|
||||
var y: array[1..2, int] = [0, 1]
|
||||
|
||||
doAssert x == y # compiles
|
||||
doAssert @[x] == @[y]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user