mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-01 03:13:41 +00:00
Compare commits
13 Commits
araq-dispo
...
pr_remove_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
241095d73f | ||
|
|
47d3fb28bd | ||
|
|
7f9c470212 | ||
|
|
91d9171278 | ||
|
|
d062c4fc70 | ||
|
|
be000b37c1 | ||
|
|
cf313fdc11 | ||
|
|
b0c509fcf8 | ||
|
|
fd98ddaa9e | ||
|
|
e93c5a635c | ||
|
|
04288236f4 | ||
|
|
1f29d5040c | ||
|
|
19fd8f5ec1 |
@@ -33,8 +33,6 @@ errors.
|
||||
|
||||
- Bitshift operators (`shl`, `shr`, `ashr`) now apply bitmasking to the right operand in the C/C++/VM/JS backends.
|
||||
|
||||
- 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
|
||||
|
||||
[//]: # "Additions:"
|
||||
@@ -114,9 +112,7 @@ errors.
|
||||
|
||||
## Tool changes
|
||||
|
||||
- Added `--raw` flag when generating JSON docs to not render markup.
|
||||
- Added `--stdinfile` flag to name of the file used when running program from stdin (defaults to `stdinfile.nim`)
|
||||
- Added `--styleCheck:warning` flag to treat style check violations as warnings.
|
||||
|
||||
## Documentation changes
|
||||
|
||||
|
||||
@@ -501,7 +501,6 @@ const
|
||||
proc idGeneratorFromModule*(m: PSym): IdGenerator =
|
||||
assert m.kind == skModule
|
||||
result = IdGenerator(module: m.itemId.module, symId: m.itemId.item, typeId: 0, disambTable: initCountTable[PIdent]())
|
||||
result.disambTable.inc m.name
|
||||
|
||||
proc idGeneratorForPackage*(nextIdWillBe: int32): IdGenerator =
|
||||
result = IdGenerator(module: PackageModuleId, symId: nextIdWillBe - 1'i32, typeId: 0, disambTable: initCountTable[PIdent]())
|
||||
@@ -550,25 +549,22 @@ proc addAllowNil*(father, son: PNode) {.inline.} =
|
||||
father.sons.add(son)
|
||||
|
||||
proc add*(father, son: PType) =
|
||||
ensureMutable father
|
||||
assert father.kind != tyProc or father.sonsImpl.len == 0
|
||||
assert son != nil
|
||||
father.sonsImpl.add son
|
||||
|
||||
proc addAllowNil*(father, son: PType) {.inline.} =
|
||||
ensureMutable father
|
||||
assert father.kind != tyProc or father.sonsImpl.len == 0
|
||||
father.sonsImpl.add son
|
||||
|
||||
proc `[]`*(n: PType, i: int): PType {.inline.} =
|
||||
template `[]`*(n: PType, i: int): PType =
|
||||
if n.state == Partial: loadType(n)
|
||||
if n.kind == tyProc and i > 0:
|
||||
assert n.nImpl[i] != nil and n.nImpl[i].sym != nil
|
||||
n.nImpl[i].sym.typ
|
||||
else:
|
||||
n.sonsImpl[i]
|
||||
|
||||
proc `[]=`*(n: PType, i: int; x: PType) {.inline.} =
|
||||
template `[]=`*(n: PType, i: int; x: PType) =
|
||||
if n.state == Partial: loadType(n)
|
||||
if n.kind == tyProc and i > 0:
|
||||
assert n.nImpl[i] != nil and n.nImpl[i].sym != nil
|
||||
@@ -576,13 +572,12 @@ proc `[]=`*(n: PType, i: int; x: PType) {.inline.} =
|
||||
else:
|
||||
n.sonsImpl[i] = x
|
||||
|
||||
proc `[]`*(n: PType, i: BackwardsIndex): PType {.inline.} =
|
||||
template `[]`*(n: PType, i: BackwardsIndex): PType =
|
||||
if n.state == Partial: loadType(n)
|
||||
n[n.sonsImpl.len - i.int]
|
||||
|
||||
proc `[]=`*(n: PType, i: BackwardsIndex; x: PType) {.inline.} =
|
||||
n[n.len - i.int]
|
||||
template `[]=`*(n: PType, i: BackwardsIndex; x: PType) =
|
||||
if n.state == Partial: loadType(n)
|
||||
n[n.sonsImpl.len - i.int] = x
|
||||
n[n.len - i.int] = x
|
||||
|
||||
proc getDeclPragma*(n: PNode): PNode =
|
||||
## return the `nkPragma` node for declaration `n`, or `nil` if no pragma was found.
|
||||
@@ -926,7 +921,7 @@ proc newProcNode*(kind: TNodeKind, info: TLineInfo, body: PNode,
|
||||
|
||||
const
|
||||
AttachedOpToStr*: array[TTypeAttachedOp, string] = [
|
||||
"=wasMoved", "=destroy", "=dispose", "=copy", "=dup", "=sink", "=trace", "=deepcopy"]
|
||||
"=wasMoved", "=destroy", "=copy", "=dup", "=sink", "=trace", "=deepcopy"]
|
||||
|
||||
proc `$`*(s: PSym): string =
|
||||
if s != nil:
|
||||
@@ -935,7 +930,6 @@ proc `$`*(s: PSym): string =
|
||||
result = "<nil>"
|
||||
|
||||
proc len*(n: PType): int {.inline.} =
|
||||
if n.state == Partial: loadType(n)
|
||||
if n.kind == tyProc:
|
||||
result = if n.nImpl == nil: 0 else: n.nImpl.len
|
||||
else:
|
||||
@@ -1174,7 +1168,6 @@ proc skipTypesOrNil*(t: PType, kinds: TTypeKinds): PType =
|
||||
## same as skipTypes but handles 'nil'
|
||||
result = t
|
||||
while result != nil and result.kind in kinds:
|
||||
if result.state == Partial: loadType(result)
|
||||
if result.sonsImpl.len == 0: return nil
|
||||
result = last(result)
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ type
|
||||
inProc: int
|
||||
#writtenTypes: seq[PType] # types written in this module, to be unloaded later
|
||||
#writtenSyms: seq[PSym] # symbols written in this module, to be unloaded later
|
||||
exports: Table[FileIndex, HashSet[string]] # module -> specific symbol names (empty = all)
|
||||
writtenPackages: HashSet[string]
|
||||
|
||||
const
|
||||
@@ -252,7 +253,6 @@ proc writeLoc(w: var Writer; dest: var TokenBuf; loc: TLoc) =
|
||||
proc writeTypeDef(w: var Writer; dest: var TokenBuf; typ: PType) =
|
||||
dest.buildTree tdefTag:
|
||||
dest.addSymDef pool.syms.getOrIncl(typeToNifSym(typ, w.infos.config)), NoLineInfo
|
||||
dest.addDotToken # always private for the index generator
|
||||
|
||||
#dest.addIdent toNifTag(typ.kind)
|
||||
writeFlags(dest, typ.flagsImpl)
|
||||
@@ -321,7 +321,7 @@ proc collectGenericParams(w: var Writer; n: PNode) =
|
||||
proc writeSymDef(w: var Writer; dest: var TokenBuf; sym: PSym) =
|
||||
dest.addParLe sdefTag, trLineInfo(w, sym.infoImpl)
|
||||
dest.addSymDef pool.syms.getOrIncl(w.toNifSymName(sym)), NoLineInfo
|
||||
if {sfExported, sfFromGeneric} * sym.flagsImpl == {sfExported}:
|
||||
if sfExported in sym.flagsImpl:
|
||||
dest.addIdent "x"
|
||||
else:
|
||||
dest.addDotToken
|
||||
@@ -345,8 +345,6 @@ proc writeSymDef(w: var Writer; dest: var TokenBuf; sym: PSym) =
|
||||
|
||||
if sym.kindImpl == skModule:
|
||||
dest.addDotToken() # position will be set by the loader!
|
||||
elif sym.kindImpl in {skVar, skLet, skForVar, skResult}:
|
||||
dest.addIntLit 0 # hack for the VM which uses this field to store information
|
||||
else:
|
||||
dest.addIntLit sym.positionImpl
|
||||
|
||||
@@ -476,41 +474,6 @@ proc trImport(w: var Writer; n: PNode) =
|
||||
w.deps.addStrLit fp # raw string literal, no wrapper needed
|
||||
w.deps.addParRi
|
||||
|
||||
proc trExport(w: var Writer; n: PNode) =
|
||||
# Collect export information for the index
|
||||
# nkExportStmt children are nkSym nodes
|
||||
# When exporting a module (export dollars), the module symbol is a child
|
||||
# followed by all symbols from that module - we use empty set to mean "export all"
|
||||
# When exporting specific symbols (export foo, bar), we collect their names
|
||||
w.deps.addParLe pool.tags.getOrIncl(toNifTag(n.kind)), trLineInfo(w, n.info)
|
||||
w.deps.addDotToken # flags
|
||||
w.deps.addDotToken # type
|
||||
for child in n:
|
||||
if child.kind == nkSym:
|
||||
let s = child.sym
|
||||
if s.kindImpl == skModule:
|
||||
discard "do not write module syms here"
|
||||
else:
|
||||
w.deps.addSymUse pool.syms.getOrIncl(w.toNifSymName(s)), NoLineInfo
|
||||
w.deps.addParRi
|
||||
|
||||
let replayTag = registerTag("replay")
|
||||
let repConverterTag = registerTag("repconverter")
|
||||
let repDestroyTag = registerTag("repdestroy")
|
||||
let repDisposeTag = registerTag("repdispose")
|
||||
let repWasMovedTag = registerTag("repwasmoved")
|
||||
let repCopyTag = registerTag("repcopy")
|
||||
let repSinkTag = registerTag("repsink")
|
||||
let repDupTag = registerTag("repdup")
|
||||
let repTraceTag = registerTag("reptrace")
|
||||
let repDeepCopyTag = registerTag("repdeepcopy")
|
||||
let repEnumToStrTag = registerTag("repenumtostr")
|
||||
let repMethodTag = registerTag("repmethod")
|
||||
#let repClassTag = registerTag("repclass")
|
||||
let includeTag = registerTag("include")
|
||||
let importTag = registerTag("import")
|
||||
let implTag = registerTag("implementation")
|
||||
|
||||
proc writeNode(w: var Writer; dest: var TokenBuf; n: PNode; forAst = false) =
|
||||
if n == nil:
|
||||
dest.addDotToken
|
||||
@@ -618,9 +581,37 @@ proc writeNode(w: var Writer; dest: var TokenBuf; n: PNode; forAst = false) =
|
||||
of nkIncludeStmt:
|
||||
trInclude w, n
|
||||
of nkExportStmt, nkExportExceptStmt:
|
||||
# Collect export information for the index
|
||||
# nkExportStmt children are nkSym nodes
|
||||
# When exporting a module (export dollars), the module symbol is a child
|
||||
# followed by all symbols from that module - we use empty set to mean "export all"
|
||||
# When exporting specific symbols (export foo, bar), we collect their names
|
||||
# Note: nkExportExceptStmt is transformed to nkExportStmt by semExportExcept,
|
||||
# but we handle both just in case
|
||||
trExport w, n
|
||||
var exportAllModules = initHashSet[FileIndex]()
|
||||
for child in n:
|
||||
if child.kind == nkSym:
|
||||
let s = child.sym
|
||||
if s.kindImpl == skModule:
|
||||
# Export all from this module - use empty set
|
||||
let modIdx = s.positionImpl.FileIndex
|
||||
exportAllModules.incl modIdx
|
||||
if modIdx notin w.exports:
|
||||
w.exports[modIdx] = initHashSet[string]() # empty means "export all"
|
||||
else:
|
||||
# Export specific symbol, but only if we're not already exporting all from this module
|
||||
let modIdx = s.itemId.module.FileIndex
|
||||
if modIdx notin exportAllModules:
|
||||
if modIdx notin w.exports:
|
||||
w.exports[modIdx] = initHashSet[string]()
|
||||
w.exports[modIdx].incl s.name.s
|
||||
# Write the export statement as a regular node
|
||||
w.withNode dest, n:
|
||||
for i in 0 ..< n.len:
|
||||
if n[i].kind == nkSym and n[i].sym.kindImpl == skModule:
|
||||
discard "do not write module syms here"
|
||||
else:
|
||||
writeNode(w, dest, n[i], forAst)
|
||||
else:
|
||||
w.withNode dest, n:
|
||||
for i in 0 ..< n.len:
|
||||
@@ -672,14 +663,46 @@ proc createStmtList(buf: var TokenBuf; info: PackedLineInfo) {.inline.} =
|
||||
buf.addDotToken # flags
|
||||
buf.addDotToken # type
|
||||
|
||||
proc buildExportBuf(w: var Writer): TokenBuf =
|
||||
## Build the export section for the NIF index from collected exports
|
||||
result = createTokenBuf(32)
|
||||
for modIdx, names in w.exports:
|
||||
let path = toFullPath(w.infos.config, modIdx)
|
||||
if names.len == 0:
|
||||
# Export all from this module
|
||||
result.addParLe(TagId(ExportIdx), NoLineInfo)
|
||||
result.add strToken(pool.strings.getOrIncl(path), NoLineInfo)
|
||||
result.addParRi()
|
||||
else:
|
||||
# Export specific symbols
|
||||
result.addParLe(TagId(FromexportIdx), NoLineInfo)
|
||||
result.add strToken(pool.strings.getOrIncl(path), NoLineInfo)
|
||||
for name in names:
|
||||
result.add identToken(pool.strings.getOrIncl(name), NoLineInfo)
|
||||
result.addParRi()
|
||||
|
||||
let replayTag = registerTag("replay")
|
||||
let repConverterTag = registerTag("repconverter")
|
||||
let repDestroyTag = registerTag("repdestroy")
|
||||
let repWasMovedTag = registerTag("repwasmoved")
|
||||
let repCopyTag = registerTag("repcopy")
|
||||
let repSinkTag = registerTag("repsink")
|
||||
let repDupTag = registerTag("repdup")
|
||||
let repTraceTag = registerTag("reptrace")
|
||||
let repDeepCopyTag = registerTag("repdeepcopy")
|
||||
let repEnumToStrTag = registerTag("repenumtostr")
|
||||
let repMethodTag = registerTag("repmethod")
|
||||
#let repClassTag = registerTag("repclass")
|
||||
let includeTag = registerTag("include")
|
||||
let importTag = registerTag("import")
|
||||
let implTag = registerTag("implementation")
|
||||
|
||||
proc writeOp(w: var Writer; content: var TokenBuf; op: LogEntry) =
|
||||
case op.kind
|
||||
of HookEntry:
|
||||
case op.op
|
||||
of attachedDestructor:
|
||||
content.addParLe repDestroyTag, NoLineInfo
|
||||
of attachedDispose:
|
||||
content.addParLe repDisposeTag, NoLineInfo
|
||||
of attachedAsgn:
|
||||
content.addParLe repCopyTag, NoLineInfo
|
||||
of attachedWasMoved:
|
||||
@@ -761,6 +784,10 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
|
||||
|
||||
writeFile(dest, d)
|
||||
|
||||
let exportBuf = buildExportBuf(w)
|
||||
createIndex(d, dest[0].info, false,
|
||||
IndexSections(exportBuf: exportBuf))
|
||||
|
||||
# --------------------------- Loader (lazy!) -----------------------------------------------
|
||||
|
||||
proc nodeKind(n: Cursor): TNodeKind {.inline.} =
|
||||
@@ -818,7 +845,7 @@ type
|
||||
NifModule = ref object
|
||||
stream: nifstreams.Stream
|
||||
symCounter: int32
|
||||
index: Table[string, NifIndexEntry] # Simple embedded index for offsets
|
||||
index: NifIndex
|
||||
suffix: string
|
||||
|
||||
DecodeContext* = object
|
||||
@@ -844,65 +871,25 @@ type
|
||||
LoadFlag* = enum
|
||||
LoadFullAst, AlwaysLoadInterface
|
||||
|
||||
proc readEmbeddedIndex(s: var Stream): Table[string, NifIndexEntry] =
|
||||
## Reads the simple embedded index (index (kv sym offset)...) from indexStartsAt position.
|
||||
result = initTable[string, NifIndexEntry]()
|
||||
let indexPos = indexStartsAt(s.r)
|
||||
if indexPos <= 0:
|
||||
return
|
||||
let contentPos = offset(s.r) # Save position
|
||||
s.r.jumpTo(indexPos)
|
||||
|
||||
var previousOffset = 0
|
||||
var t = next(s)
|
||||
let exportedTagId = pool.tags.getOrIncl("x")
|
||||
if t.kind == ParLe and pool.tags[t.tagId] == ".index":
|
||||
t = next(s)
|
||||
while t.kind != EofToken and t.kind != ParRi:
|
||||
if t.kind == ParLe:
|
||||
let vis = if t.tagId == exportedTagId: Exported else: Hidden
|
||||
let info = t.info
|
||||
t = next(s) # skip (kv
|
||||
var key = ""
|
||||
if t.kind == Symbol:
|
||||
key = pool.syms[t.symId]
|
||||
elif t.kind == Ident:
|
||||
key = pool.strings[t.litId]
|
||||
t = next(s) # skip symbol
|
||||
if t.kind == IntLit:
|
||||
let offset = int(pool.integers[t.intId]) + previousOffset
|
||||
result[key] = NifIndexEntry(offset: offset, info: info, vis: vis)
|
||||
previousOffset = offset
|
||||
t = next(s) # skip offset
|
||||
if t.kind == ParRi:
|
||||
t = next(s) # skip )
|
||||
else:
|
||||
t = next(s)
|
||||
|
||||
s.r.jumpTo(contentPos) # Restore position
|
||||
|
||||
proc moduleId(c: var DecodeContext; suffix: string; flags: set[LoadFlag] = {}): FileIndex =
|
||||
var isKnownFile = false
|
||||
result = c.infos.config.registerNifSuffix(suffix, isKnownFile)
|
||||
# Always load the module's index if it's not already in c.mods
|
||||
# This is needed when resolving symbols from modules that were registered elsewhere
|
||||
# but haven't had their NIF index loaded yet
|
||||
let hasEntry = c.mods.hasKey(result)
|
||||
if not hasEntry or AlwaysLoadInterface in flags:
|
||||
if not isKnownFile or AlwaysLoadInterface in flags:
|
||||
let modFile = (getNimcacheDir(c.infos.config) / RelativeFile(suffix & ".nif")).string
|
||||
let idxFile = (getNimcacheDir(c.infos.config) / RelativeFile(suffix & ".s.idx.nif")).string
|
||||
if not fileExists(modFile):
|
||||
raiseAssert "NIF file not found for module suffix '" & suffix & "': " & modFile &
|
||||
". This can happen when loading a module from NIF that references another module " &
|
||||
"whose NIF file hasn't been written yet."
|
||||
var stream = nifstreams.open(modFile)
|
||||
let index = readEmbeddedIndex(stream)
|
||||
c.mods[result] = NifModule(stream: stream, index: index, suffix: suffix)
|
||||
c.mods[result] = NifModule(stream: nifstreams.open(modFile), index: readIndex(idxFile), suffix: suffix)
|
||||
|
||||
proc getOffset(c: var DecodeContext; module: FileIndex; nifName: string): NifIndexEntry =
|
||||
let ii = addr c.mods[module].index
|
||||
result = ii[].getOrDefault(nifName)
|
||||
result = ii.public.getOrDefault(nifName)
|
||||
if result.offset == 0:
|
||||
raiseAssert "symbol has no offset: " & nifName
|
||||
result = ii.private.getOrDefault(nifName)
|
||||
if result.offset == 0:
|
||||
raiseAssert "symbol has no offset: " & nifName
|
||||
|
||||
proc loadNode(c: var DecodeContext; n: var Cursor; thisModule: string;
|
||||
localSyms: var Table[string, PSym]): PNode
|
||||
@@ -1094,8 +1081,6 @@ proc loadTypeFromCursor(c: var DecodeContext; n: var Cursor; t: PType; localSyms
|
||||
expect n, SymbolDef
|
||||
# ignore the type's name, we have already used it to create this PType's itemId!
|
||||
inc n
|
||||
expect n, DotToken
|
||||
inc n
|
||||
#loadField t.kind
|
||||
loadField t.flagsImpl
|
||||
loadField t.callConvImpl
|
||||
@@ -1410,33 +1395,83 @@ proc extractBasename(nifName: string): string =
|
||||
proc populateInterfaceTablesFromIndex(c: var DecodeContext; module: FileIndex;
|
||||
interf, interfHidden: var TStrTable; thisModule: string) =
|
||||
## Populates interface tables from the NIF index structure.
|
||||
## Uses the simple embedded index for offsets, exports passed from processTopLevel.
|
||||
## Uses the index's public/private tables instead of traversing AST.
|
||||
|
||||
# Move the index table out to avoid iterator invalidation
|
||||
# Move the public table and exports list out to avoid iterator invalidation
|
||||
# (moduleId can add to c.mods which would invalidate Table iterators)
|
||||
var indexTab = move c.mods[module].index
|
||||
# We move them back after iteration.
|
||||
var publicTab = move c.mods[module].index.public
|
||||
var exportsList = move c.mods[module].index.exports
|
||||
|
||||
# Add all symbols to interf (exported interface) and interfHidden
|
||||
for nifName, entry in indexTab:
|
||||
if entry.vis == Exported:
|
||||
let sym = loadSymFromIndexEntry(c, module, nifName, entry, thisModule)
|
||||
if sym != nil:
|
||||
strTableAdd(interf, sym)
|
||||
strTableAdd(interfHidden, sym)
|
||||
elif not nifName.startsWith("`t"):
|
||||
# Add all public symbols to interf (exported interface) and interfHidden
|
||||
for nifName, entry in publicTab:
|
||||
if not nifName.startsWith("`t"):
|
||||
# do not load types, they are not part of an interface but an implementation detail!
|
||||
#echo "LOADING SYM ", nifName, " ", entry.offset
|
||||
let sym = loadSymFromIndexEntry(c, module, nifName, entry, thisModule)
|
||||
if sym != nil:
|
||||
strTableAdd(interf, sym)
|
||||
strTableAdd(interfHidden, sym)
|
||||
|
||||
# Move index table back
|
||||
c.mods[module].index = move indexTab
|
||||
# Move public table back
|
||||
c.mods[module].index.public = move publicTab
|
||||
|
||||
# Process exports (re-exports from other modules)
|
||||
for exp in exportsList:
|
||||
let (path, kind, names) = exp
|
||||
# Convert path to module suffix
|
||||
let expSuffix = moduleSuffix(path, cast[seq[string]](c.infos.config.searchPaths))
|
||||
# Load the exported module's index
|
||||
let expModule = moduleId(c, expSuffix)
|
||||
|
||||
# Move the exported module's public table out to avoid iterator invalidation
|
||||
var expPublicTab = move c.mods[expModule].index.public
|
||||
|
||||
# Build a set of names for filtering
|
||||
var nameSet = initHashSet[string]()
|
||||
for nameId in names:
|
||||
nameSet.incl pool.strings[nameId]
|
||||
|
||||
# Add symbols based on export kind
|
||||
for nifName, entry in expPublicTab:
|
||||
if nifName.startsWith("`t"):
|
||||
continue # skip types
|
||||
|
||||
let basename = extractBasename(nifName)
|
||||
let shouldInclude =
|
||||
case kind
|
||||
of ExportIdx: true # export all
|
||||
of FromexportIdx: basename in nameSet # only specific names
|
||||
of ExportexceptIdx: basename notin nameSet # all except specific names
|
||||
else: false
|
||||
|
||||
if shouldInclude:
|
||||
let sym = loadSymFromIndexEntry(c, expModule, nifName, entry, expSuffix)
|
||||
if sym != nil:
|
||||
strTableAdd(interf, sym)
|
||||
strTableAdd(interfHidden, sym)
|
||||
|
||||
# Move exported module's public table back
|
||||
c.mods[expModule].index.public = move expPublicTab
|
||||
|
||||
# Move exports list back
|
||||
c.mods[module].index.exports = move exportsList
|
||||
|
||||
when false:
|
||||
# Add private symbols to interfHidden only
|
||||
for nifName, entry in idx.private:
|
||||
let sym = loadSymFromIndexEntry(c, module, nifName, entry, thisModule)
|
||||
if sym != nil:
|
||||
strTableAdd(interfHidden, sym)
|
||||
|
||||
proc toNifFilename*(conf: ConfigRef; f: FileIndex): string =
|
||||
let suffix = moduleSuffix(conf, f)
|
||||
result = toGeneratedFile(conf, AbsoluteFile(suffix), ".nif").string
|
||||
|
||||
proc toNifIndexFilename*(conf: ConfigRef; f: FileIndex): string =
|
||||
let suffix = moduleSuffix(conf, f)
|
||||
result = toGeneratedFile(conf, AbsoluteFile(suffix), ".s.idx.nif").string
|
||||
|
||||
proc resolveSym(c: var DecodeContext; symAsStr: string; alsoConsiderPrivate: bool): PSym =
|
||||
result = c.syms.getOrDefault(symAsStr)[0]
|
||||
if result != nil:
|
||||
@@ -1447,17 +1482,14 @@ proc resolveSym(c: var DecodeContext; symAsStr: string; alsoConsiderPrivate: boo
|
||||
return nil # Local symbols shouldn't be hooks
|
||||
let module = moduleId(c, sn.module)
|
||||
# Look up the symbol in the module's index
|
||||
# Try both formats: with module suffix (e.g., "foo.0.modulename") and without (e.g., "foo.0.")
|
||||
# NIF spec allows local symbols to be stored without module suffix
|
||||
var offs = c.mods[module].index.getOrDefault(symAsStr)
|
||||
var offs = c.mods[module].index.public.getOrDefault(symAsStr)
|
||||
if offs.offset == 0:
|
||||
# Try the format without module suffix
|
||||
let localKey = sn.name & "." & $sn.count & "."
|
||||
offs = c.mods[module].index.getOrDefault(localKey)
|
||||
if offs.offset == 0:
|
||||
return nil
|
||||
if not alsoConsiderPrivate and offs.vis == Hidden:
|
||||
return nil
|
||||
if alsoConsiderPrivate:
|
||||
offs = c.mods[module].index.private.getOrDefault(symAsStr)
|
||||
if offs.offset == 0:
|
||||
return nil
|
||||
else:
|
||||
return nil
|
||||
# Create a stub symbol
|
||||
let val = addr c.mods[module].symCounter
|
||||
inc val[]
|
||||
@@ -1549,14 +1581,12 @@ proc loadImport(c: var DecodeContext; s: var Stream; deps: var seq[ModuleSuffix]
|
||||
else:
|
||||
raiseAssert "expected ParRi but got " & $tok.kind
|
||||
|
||||
proc processTopLevel(c: var DecodeContext; s: var Stream; flags: set[LoadFlag];
|
||||
interf: var TStrTable; suffix: string; module: int): PrecompiledModule =
|
||||
proc processTopLevel(c: var DecodeContext; s: var Stream; flags: set[LoadFlag] = {}; suffix: string; module: int): PrecompiledModule =
|
||||
result = PrecompiledModule(topLevel: newNode(nkStmtList))
|
||||
var localSyms = initTable[string, PSym]()
|
||||
|
||||
var t = next(s) # skip dot
|
||||
var cont = true
|
||||
let exportTag = pool.tags.getOrIncl"export"
|
||||
while cont and t.kind != EofToken:
|
||||
if t.kind == ParLe:
|
||||
if t.tagId == replayTag:
|
||||
@@ -1579,8 +1609,6 @@ proc processTopLevel(c: var DecodeContext; s: var Stream; flags: set[LoadFlag];
|
||||
t = loadLogOp(c, result.logOps, s, ConverterEntry, attachedTrace, module)
|
||||
elif t.tagId == repDestroyTag:
|
||||
t = loadLogOp(c, result.logOps, s, HookEntry, attachedDestructor, module)
|
||||
elif t.tagId == repDisposeTag:
|
||||
t = loadLogOp(c, result.logOps, s, HookEntry, attachedDispose, module)
|
||||
elif t.tagId == repWasMovedTag:
|
||||
t = loadLogOp(c, result.logOps, s, HookEntry, attachedWasMoved, module)
|
||||
elif t.tagId == repCopyTag:
|
||||
@@ -1599,24 +1627,6 @@ proc processTopLevel(c: var DecodeContext; s: var Stream; flags: set[LoadFlag];
|
||||
t = loadLogOp(c, result.logOps, s, MethodEntry, attachedTrace, module)
|
||||
#elif t.tagId == repClassTag:
|
||||
# t = loadLogOp(c, logOps, s, ClassEntry, attachedTrace, module)
|
||||
elif t.tagId == exportTag:
|
||||
t = next(s) # skip (export
|
||||
if t.kind == DotToken:
|
||||
t = next(s) # skip dot
|
||||
if t.kind == DotToken:
|
||||
t = next(s) # skip dot
|
||||
while true:
|
||||
if t.kind == Symbol:
|
||||
let symAsStr = pool.syms[t.symId]
|
||||
let sym = resolveSym(c, symAsStr, false)
|
||||
if sym != nil:
|
||||
strTableAdd(interf, sym)
|
||||
t = next(s)
|
||||
elif t.kind == ParRi:
|
||||
break
|
||||
else:
|
||||
raiseAssert "expected Symbol or ParRi but got " & $t.kind
|
||||
t = next(s)
|
||||
elif t.tagId == includeTag:
|
||||
t = skipTree(s)
|
||||
elif t.tagId == importTag:
|
||||
@@ -1639,25 +1649,25 @@ proc processTopLevel(c: var DecodeContext; s: var Stream; flags: set[LoadFlag];
|
||||
|
||||
proc loadNifModule*(c: var DecodeContext; suffix: ModuleSuffix; interf, interfHidden: var TStrTable;
|
||||
flags: set[LoadFlag] = {}): PrecompiledModule =
|
||||
# Ensure module index is loaded - moduleId returns the FileIndex for this suffix
|
||||
# Ensure module index is loaded - moduleId returns the FileIndex for this suffix
|
||||
let module = moduleId(c, string(suffix), flags)
|
||||
|
||||
# Populate interface tables from the NIF index structure
|
||||
# Symbols are created as stubs (Partial state) and will be loaded lazily via loadSym
|
||||
populateInterfaceTablesFromIndex(c, module, interf, interfHidden, string(suffix))
|
||||
|
||||
# Load the module AST (or just replay actions if loadFullAst is false)
|
||||
# processTopLevel also collects export instructions
|
||||
let s = addr c.mods[module].stream
|
||||
s.r.jumpTo 0 # Start from beginning
|
||||
discard processDirectives(s.r)
|
||||
var t = next(s[])
|
||||
if t.kind == ParLe and pool.tags[t.tagId] == toNifTag(nkStmtList):
|
||||
t = next(s[]) # skip (stmts
|
||||
t = next(s[]) # skip flags
|
||||
result = processTopLevel(c, s[], flags, interf, string(suffix), module.int)
|
||||
result = processTopLevel(c, s[], flags, string(suffix), module.int)
|
||||
else:
|
||||
result = PrecompiledModule(topLevel: newNode(nkStmtList))
|
||||
|
||||
# Populate interface tables from the NIF index structure
|
||||
# Symbols are created as stubs (Partial state) and will be loaded lazily via loadSym
|
||||
# Use exports collected by processTopLevel
|
||||
populateInterfaceTablesFromIndex(c, module, interf, interfHidden, string(suffix))
|
||||
|
||||
proc loadNifModule*(c: var DecodeContext; f: FileIndex; interf, interfHidden: var TStrTable;
|
||||
flags: set[LoadFlag] = {}): PrecompiledModule =
|
||||
let suffix = ModuleSuffix(moduleSuffix(c.infos.config, f))
|
||||
|
||||
@@ -764,7 +764,6 @@ type
|
||||
attachedAsgn,
|
||||
attachedDup,
|
||||
attachedSink,
|
||||
attachedDispose,
|
||||
attachedTrace,
|
||||
attachedDeepCopy
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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:
|
||||
@@ -294,12 +294,7 @@ proc cacheGetType(tab: TypeCache; sig: SigHash): Rope =
|
||||
result = tab.getOrDefault(sig)
|
||||
|
||||
proc addAbiCheck(m: BModule; t: PType, name: Rope) =
|
||||
if isDefined(m.config, "checkAbi") and (let size = getSize(m.config, t); size != szUnknownSize) and
|
||||
not (t.kind == tyObject and searchTypeFor(t, proc (t: PType): bool {.nimcall.} = t.kind == tyUncheckedArray)):
|
||||
# `UncheckedArray`, not `ptr UncheckedArray` type field in object types is a flexible array.
|
||||
# `sizeof` in C and Nim doesn't always return the same value for object types containing it.
|
||||
# making `getSize` in Nim always returns the same value as `sizeof` in C from flexible arrays seems hard.
|
||||
# See `SEQ_DECL_SIZE` in lib/nimbase.h
|
||||
if isDefined(m.config, "checkAbi") and (let size = getSize(m.config, t); size != szUnknownSize):
|
||||
var msg = "backend & Nim disagree on size for: "
|
||||
msg.addTypeHeader(m.config, t)
|
||||
var msg2 = ""
|
||||
@@ -1072,7 +1067,6 @@ proc getTypeDescAux(m: BModule; origTyp: PType, check: var IntSet; kind: TypeDes
|
||||
else: getTupleDesc(m, t, result, check)
|
||||
if not isImportedType(t):
|
||||
m.s[cfsTypes].add(recdesc)
|
||||
addAbiCheck(m, t, result)
|
||||
elif tfIncompleteStruct notin t.flags:
|
||||
discard # addAbiCheck(m, t, result) # already handled elsewhere
|
||||
of tySet:
|
||||
@@ -1692,7 +1686,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'.
|
||||
@@ -1855,10 +1849,6 @@ proc genTypeInfoV2Impl(m: BModule; t, origType: PType, name: Rope; info: TLineIn
|
||||
typeEntry.addCast(CPointer):
|
||||
genHook(m, t, info, attachedTrace, typeEntry)
|
||||
|
||||
typeEntry.addField(typeInit, name = "disposeImpl"):
|
||||
typeEntry.addCast(CPointer):
|
||||
genHook(m, t, info, attachedDispose, typeEntry)
|
||||
|
||||
let dispatchMethods = toSeq(getMethodsPerType(m.g.graph, t))
|
||||
if dispatchMethods.len > 0:
|
||||
typeEntry.addField(typeInit, name = "flags"):
|
||||
@@ -2006,7 +1996,7 @@ proc genTypeInfoV1(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
owner = m.module.position.int32
|
||||
|
||||
m.g.typeInfoMarker[sig] = (str: result, owner: owner)
|
||||
#rememberEmittedTypeInfo(m.g.graph, FileIndex(owner), $result)
|
||||
rememberEmittedTypeInfo(m.g.graph, FileIndex(owner), $result)
|
||||
|
||||
case t.kind
|
||||
of tyEmpty, tyVoid: result = cIntValue(0)
|
||||
|
||||
@@ -30,6 +30,7 @@ when not defined(leanCompiler):
|
||||
|
||||
import std/strutils except `%`, addf # collides with ropes.`%`
|
||||
|
||||
from ic / ic import ModuleBackendFlag
|
||||
import std/[dynlib, math, tables, sets, os, intsets, hashes]
|
||||
|
||||
const
|
||||
@@ -1332,7 +1333,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 +1688,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 +1700,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 +1735,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 +1762,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 +1865,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)
|
||||
@@ -1921,6 +1926,36 @@ proc genMainProc(m: BModule) =
|
||||
if m.config.cppCustomNamespace.len > 0:
|
||||
openNamespaceNim(m.config.cppCustomNamespace, m.s[cfsProcs])
|
||||
|
||||
proc registerInitProcs*(g: BModuleList; m: PSym; flags: set[ModuleBackendFlag]) =
|
||||
## Called from the IC backend.
|
||||
if HasDatInitProc in flags:
|
||||
let datInit = getSomeNameForModule(g.config, g.config.toFullPath(m.info.fileIndex).AbsoluteFile) & "DatInit000"
|
||||
g.mainModProcs.addDeclWithVisibility(Private):
|
||||
g.mainModProcs.addProcHeader(ccNimCall, datInit, CVoid, cProcParams())
|
||||
g.mainModProcs.finishProcHeaderAsProto()
|
||||
g.mainDatInit.addCallStmt(datInit)
|
||||
if HasModuleInitProc in flags:
|
||||
let init = getSomeNameForModule(g.config, g.config.toFullPath(m.info.fileIndex).AbsoluteFile) & "Init000"
|
||||
g.mainModProcs.addDeclWithVisibility(Private):
|
||||
g.mainModProcs.addProcHeader(ccNimCall, init, CVoid, cProcParams())
|
||||
g.mainModProcs.finishProcHeaderAsProto()
|
||||
if sfMainModule in m.flags:
|
||||
g.mainModInit.addCallStmt(init)
|
||||
elif sfSystemModule in m.flags:
|
||||
g.mainDatInit.addCallStmt(init) # systemInit must called right after systemDatInit if any
|
||||
else:
|
||||
g.otherModsInit.addCallStmt(init)
|
||||
|
||||
proc whichInitProcs*(m: BModule): set[ModuleBackendFlag] =
|
||||
# called from IC.
|
||||
result = {}
|
||||
if m.hcrOn or m.preInitProc.s(cpsInit).buf.len > 0 or m.preInitProc.s(cpsStmts).buf.len > 0:
|
||||
result.incl HasModuleInitProc
|
||||
for i in cfsTypeInit1..cfsDynLibInit:
|
||||
if m.s[i].buf.len != 0:
|
||||
result.incl HasDatInitProc
|
||||
break
|
||||
|
||||
proc registerModuleToMain(g: BModuleList; m: BModule) =
|
||||
let
|
||||
init = m.getInitName
|
||||
@@ -2030,7 +2065,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 +2634,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 +2642,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)
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ const
|
||||
errInvalidCmdLineOption = "invalid command line option: '$1'"
|
||||
errOnOrOffExpectedButXFound = "'on' or 'off' expected, but '$1' found"
|
||||
errOnOffOrListExpectedButXFound = "'on', 'off' or 'list' expected, but '$1' found"
|
||||
errOffHintsError = "'off', 'hint', 'warning', 'error' or 'usages' expected, but '$1' found"
|
||||
errOffHintsError = "'off', 'hint', 'error' or 'usages' expected, but '$1' found"
|
||||
|
||||
proc invalidCmdLineOption(conf: ConfigRef; pass: TCmdLinePass, switch: string, info: TLineInfo) =
|
||||
if switch == " ": localError(conf, info, errInvalidCmdLineOption % "-")
|
||||
@@ -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
|
||||
@@ -495,6 +494,7 @@ proc parseCommand*(command: string): Command =
|
||||
of "gendepend": cmdGendepend
|
||||
of "dump": cmdDump
|
||||
of "parse": cmdParse
|
||||
of "rod": cmdRod
|
||||
of "secret": cmdInteractive
|
||||
of "nop", "help": cmdNop
|
||||
of "jsonscript": cmdJsonscript
|
||||
@@ -571,7 +571,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 +604,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")
|
||||
@@ -1114,9 +1109,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
of "shownonexports":
|
||||
expectNoArg(conf, switch, arg, pass, info)
|
||||
showNonExportedFields(conf)
|
||||
of "raw":
|
||||
expectNoArg(conf, switch, arg, pass, info)
|
||||
docRawOutput(conf)
|
||||
of "exceptions":
|
||||
case arg.normalize
|
||||
of "cpp": conf.exc = excCpp
|
||||
@@ -1148,10 +1140,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
defineSymbol(conf.symbols, "nimSeqsV2")
|
||||
of "stylecheck":
|
||||
case arg.normalize
|
||||
of "off": conf.globalOptions = conf.globalOptions - {optStyleHint, optStyleError, optStyleWarning}
|
||||
of "hint": conf.globalOptions = conf.globalOptions + {optStyleHint} - {optStyleError, optStyleWarning}
|
||||
of "warning": conf.globalOptions = conf.globalOptions + {optStyleWarning} - {optStyleHint, optStyleError}
|
||||
of "error": conf.globalOptions = conf.globalOptions + {optStyleError} - {optStyleHint, optStyleWarning}
|
||||
of "off": conf.globalOptions = conf.globalOptions - {optStyleHint, optStyleError}
|
||||
of "hint": conf.globalOptions = conf.globalOptions + {optStyleHint} - {optStyleError}
|
||||
of "error": conf.globalOptions = conf.globalOptions + {optStyleError}
|
||||
of "usages": conf.globalOptions.incl optStyleUsages
|
||||
else: localError(conf, info, errOffHintsError % arg)
|
||||
of "showallmismatches":
|
||||
|
||||
@@ -175,5 +175,3 @@ proc initDefines*(symbols: StringTableRef) =
|
||||
defineSymbol("nimHasSetLengthSeqUninitMagic")
|
||||
defineSymbol("nimHasPreviewDuplicateModuleError")
|
||||
|
||||
defineSymbol("nimHasImplicitRangeConversion")
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ type
|
||||
nodes: seq[Node]
|
||||
processedModules: Table[string, int] # modname -> node index
|
||||
includeStack: seq[string]
|
||||
systemNodeId: int # ID of the system.nim node
|
||||
|
||||
proc toPair(c: DepContext; f: string): FilePair =
|
||||
FilePair(nimFile: f, modname: moduleSuffix(f, cast[seq[string]](c.config.searchPaths)))
|
||||
@@ -129,9 +128,6 @@ proc processImport(c: var DepContext; importPath: string; current: Node) =
|
||||
# New module - create node and process it
|
||||
let newNode = Node(files: @[pair], id: c.nodes.len)
|
||||
current.deps.add newNode.id
|
||||
# Every module depends on system.nim
|
||||
if c.systemNodeId >= 0:
|
||||
newNode.deps.add c.systemNodeId
|
||||
c.processedModules[pair.modname] = newNode.id
|
||||
c.nodes.add newNode
|
||||
traverseDeps(c, pair, newNode)
|
||||
@@ -227,9 +223,9 @@ proc traverseDeps(c: var DepContext; pair: FilePair; current: Node) =
|
||||
|
||||
proc generateBuildFile(c: DepContext): string =
|
||||
## Generate the .build.nif file for nifmake
|
||||
let nimcache = getNimcacheDir(c.config).string
|
||||
createDir(nimcache)
|
||||
result = nimcache / c.nodes[0].files[0].modname & ".build.nif"
|
||||
createDir("nifcache")
|
||||
result = "nifcache" / c.nodes[0].files[0].modname & ".build.nif"
|
||||
#getNimcacheDir(c.config).string / c.nodes[0].files[0].modname & ".build.nif"
|
||||
|
||||
var b = nifbuilder.open(result)
|
||||
defer: b.close()
|
||||
@@ -254,7 +250,7 @@ proc generateBuildFile(c: DepContext): string =
|
||||
b.addSymbolDef "nim_m"
|
||||
b.addStrLit getAppFilename()
|
||||
b.addStrLit "m"
|
||||
b.addStrLit "--nimcache:" & nimcache
|
||||
b.addStrLit "--nimcache:nifcache"
|
||||
# Add search paths
|
||||
for p in c.config.searchPaths:
|
||||
b.addStrLit "--path:" & p.string
|
||||
@@ -269,7 +265,7 @@ proc generateBuildFile(c: DepContext): string =
|
||||
b.addSymbolDef "nim_nifc"
|
||||
b.addStrLit getAppFilename()
|
||||
b.addStrLit "nifc"
|
||||
b.addStrLit "--nimcache:" & nimcache
|
||||
b.addStrLit "--nimcache:nifcache"
|
||||
# Add search paths
|
||||
for p in c.config.searchPaths:
|
||||
b.addStrLit "--path:" & p.string
|
||||
@@ -358,8 +354,7 @@ proc commandIc*(conf: ConfigRef) =
|
||||
nifler: nifler,
|
||||
nodes: @[],
|
||||
processedModules: initTable[string, int](),
|
||||
includeStack: @[],
|
||||
systemNodeId: -1
|
||||
includeStack: @[]
|
||||
)
|
||||
|
||||
# Create root node for main project file
|
||||
@@ -371,7 +366,6 @@ proc commandIc*(conf: ConfigRef) =
|
||||
# model the system.nim dependency:
|
||||
let sysNode = Node(files: @[toPair(c, (conf.libpath / RelativeFile"system.nim").string)], id: 1)
|
||||
c.nodes.add sysNode
|
||||
c.systemNodeId = sysNode.id
|
||||
rootNode.deps.add sysNode.id
|
||||
|
||||
# Process dependencies
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -433,9 +433,6 @@ proc getVarIdx(varnames: openArray[string], id: string): int =
|
||||
|
||||
proc genComment(d: PDoc, n: PNode): PRstNode =
|
||||
if n.comment.len > 0:
|
||||
if optDocRaw in d.conf.globalOptions:
|
||||
return newRstLeaf(n.comment)
|
||||
|
||||
d.sharedState.currFileIdx = addRstFileIndex(d, n.info)
|
||||
try:
|
||||
result = parseRst(n.comment,
|
||||
@@ -1179,12 +1176,8 @@ proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind, nonExports = false):
|
||||
"col": %n.info.col}
|
||||
)
|
||||
if comm != nil:
|
||||
if optDocRaw in d.conf.globalOptions:
|
||||
result.json["description"] = %comm.text
|
||||
else:
|
||||
result.rst = comm
|
||||
result.rstField = "description"
|
||||
|
||||
result.rst = comm
|
||||
result.rstField = "description"
|
||||
if r.buf.len > 0:
|
||||
result.json["code"] = %r.buf
|
||||
if k in routineKinds:
|
||||
@@ -1425,7 +1418,7 @@ proc generateDoc*(d: PDoc, n, orig: PNode, config: ConfigRef, docFlags: DocFlags
|
||||
of nkExportExceptStmt: discard "transformed into nkExportStmt by semExportExcept"
|
||||
of nkFromStmt, nkImportExceptStmt: traceDeps(d, n[0])
|
||||
of nkCallKinds:
|
||||
var comm = default(ItemPre)
|
||||
var comm: ItemPre = default(ItemPre)
|
||||
getAllRunnableExamples(d, n, comm)
|
||||
if comm.len != 0: d.modDescPre.add(comm)
|
||||
else: discard
|
||||
|
||||
178
compiler/ic/bitabs.nim
Normal file
178
compiler/ic/bitabs.nim
Normal file
@@ -0,0 +1,178 @@
|
||||
## A BiTable is a table that can be seen as an optimized pair
|
||||
## of `(Table[LitId, Val], Table[Val, LitId])`.
|
||||
|
||||
import std/hashes
|
||||
import rodfiles
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
type
|
||||
LitId* = distinct uint32
|
||||
|
||||
BiTable*[T] = object
|
||||
vals: seq[T] # indexed by LitId
|
||||
keys: seq[LitId] # indexed by hash(val)
|
||||
|
||||
proc initBiTable*[T](): BiTable[T] = BiTable[T](vals: @[], keys: @[])
|
||||
|
||||
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
|
||||
result = (h + 1) and maxHash
|
||||
|
||||
template maxHash(t): untyped = high(t.keys)
|
||||
template isFilled(x: LitId): bool = x.uint32 > 0'u32
|
||||
|
||||
proc `$`*(x: LitId): string {.borrow.}
|
||||
proc `<`*(x, y: LitId): bool {.borrow.}
|
||||
proc `<=`*(x, y: LitId): bool {.borrow.}
|
||||
proc `==`*(x, y: LitId): bool {.borrow.}
|
||||
proc hash*(x: LitId): Hash {.borrow.}
|
||||
|
||||
|
||||
proc len*[T](t: BiTable[T]): int = t.vals.len
|
||||
|
||||
proc mustRehash(length, counter: int): bool {.inline.} =
|
||||
assert(length > counter)
|
||||
result = (length * 2 < counter * 3) or (length - counter < 4)
|
||||
|
||||
const
|
||||
idStart = 1
|
||||
|
||||
template idToIdx(x: LitId): int = x.int - idStart
|
||||
|
||||
proc hasLitId*[T](t: BiTable[T]; x: LitId): bool =
|
||||
let idx = idToIdx(x)
|
||||
result = idx >= 0 and idx < t.vals.len
|
||||
|
||||
proc enlarge[T](t: var BiTable[T]) =
|
||||
var n: seq[LitId]
|
||||
newSeq(n, len(t.keys) * 2)
|
||||
swap(t.keys, n)
|
||||
for i in 0..high(n):
|
||||
let eh = n[i]
|
||||
if isFilled(eh):
|
||||
var j = hash(t.vals[idToIdx eh]) and maxHash(t)
|
||||
while isFilled(t.keys[j]):
|
||||
j = nextTry(j, maxHash(t))
|
||||
t.keys[j] = move n[i]
|
||||
|
||||
proc getKeyId*[T](t: BiTable[T]; v: T): LitId =
|
||||
let origH = hash(v)
|
||||
var h = origH and maxHash(t)
|
||||
if t.keys.len != 0:
|
||||
while true:
|
||||
let litId = t.keys[h]
|
||||
if not isFilled(litId): break
|
||||
if t.vals[idToIdx t.keys[h]] == v: return litId
|
||||
h = nextTry(h, maxHash(t))
|
||||
return LitId(0)
|
||||
|
||||
proc getOrIncl*[T](t: var BiTable[T]; v: T): LitId =
|
||||
let origH = hash(v)
|
||||
var h = origH and maxHash(t)
|
||||
if t.keys.len != 0:
|
||||
while true:
|
||||
let litId = t.keys[h]
|
||||
if not isFilled(litId): break
|
||||
if t.vals[idToIdx t.keys[h]] == v: return litId
|
||||
h = nextTry(h, maxHash(t))
|
||||
# not found, we need to insert it:
|
||||
if mustRehash(t.keys.len, t.vals.len):
|
||||
enlarge(t)
|
||||
# recompute where to insert:
|
||||
h = origH and maxHash(t)
|
||||
while true:
|
||||
let litId = t.keys[h]
|
||||
if not isFilled(litId): break
|
||||
h = nextTry(h, maxHash(t))
|
||||
else:
|
||||
setLen(t.keys, 16)
|
||||
h = origH and maxHash(t)
|
||||
|
||||
result = LitId(t.vals.len + idStart)
|
||||
t.keys[h] = result
|
||||
t.vals.add v
|
||||
|
||||
|
||||
proc `[]`*[T](t: var BiTable[T]; litId: LitId): var T {.inline.} =
|
||||
let idx = idToIdx litId
|
||||
assert idx < t.vals.len
|
||||
result = t.vals[idx]
|
||||
|
||||
proc `[]`*[T](t: BiTable[T]; litId: LitId): lent T {.inline.} =
|
||||
let idx = idToIdx litId
|
||||
assert idx < t.vals.len
|
||||
result = t.vals[idx]
|
||||
|
||||
proc hash*[T](t: BiTable[T]): Hash =
|
||||
## as the keys are hashes of the values, we simply use them instead
|
||||
var h: Hash = 0
|
||||
for i, n in pairs t.keys:
|
||||
h = h !& hash((i, n))
|
||||
result = !$h
|
||||
|
||||
proc store*[T](f: var RodFile; t: BiTable[T]) =
|
||||
storeSeq(f, t.vals)
|
||||
storeSeq(f, t.keys)
|
||||
|
||||
proc load*[T](f: var RodFile; t: var BiTable[T]) =
|
||||
loadSeq(f, t.vals)
|
||||
loadSeq(f, t.keys)
|
||||
|
||||
proc sizeOnDisc*(t: BiTable[string]): int =
|
||||
result = 4
|
||||
for x in t.vals:
|
||||
result += x.len + 4
|
||||
result += t.keys.len * sizeof(LitId)
|
||||
|
||||
when isMainModule:
|
||||
|
||||
var t: BiTable[string]
|
||||
|
||||
echo getOrIncl(t, "hello")
|
||||
|
||||
echo getOrIncl(t, "hello")
|
||||
echo getOrIncl(t, "hello3")
|
||||
echo getOrIncl(t, "hello4")
|
||||
echo getOrIncl(t, "helloasfasdfdsa")
|
||||
echo getOrIncl(t, "hello")
|
||||
echo getKeyId(t, "hello")
|
||||
echo getKeyId(t, "none")
|
||||
|
||||
for i in 0 ..< 100_000:
|
||||
discard t.getOrIncl($i & "___" & $i)
|
||||
|
||||
for i in 0 ..< 100_000:
|
||||
assert t.getOrIncl($i & "___" & $i).idToIdx == i + 4
|
||||
echo "begin"
|
||||
echo t.vals.len
|
||||
|
||||
echo t.vals[0]
|
||||
echo t.vals[1004]
|
||||
|
||||
echo "middle"
|
||||
|
||||
var tf: BiTable[float]
|
||||
|
||||
discard tf.getOrIncl(0.4)
|
||||
discard tf.getOrIncl(16.4)
|
||||
discard tf.getOrIncl(32.4)
|
||||
echo getKeyId(tf, 32.4)
|
||||
|
||||
var f2 = open("testblah.bin", fmWrite)
|
||||
echo store(f2, tf)
|
||||
f2.close
|
||||
|
||||
var f1 = open("testblah.bin", fmRead)
|
||||
|
||||
var t2: BiTable[float]
|
||||
|
||||
echo f1.load(t2)
|
||||
echo t2.vals.len
|
||||
|
||||
echo getKeyId(t2, 32.4)
|
||||
|
||||
echo "end"
|
||||
|
||||
|
||||
f1.close
|
||||
179
compiler/ic/cbackend.nim
Normal file
179
compiler/ic/cbackend.nim
Normal file
@@ -0,0 +1,179 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2021 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## New entry point into our C/C++ code generator. Ideally
|
||||
## somebody would rewrite the old backend (which is 8000 lines of crufty Nim code)
|
||||
## to work on packed trees directly and produce the C code as an AST which can
|
||||
## then be rendered to text in a very simple manner. Unfortunately nobody wrote
|
||||
## this code. So instead we wrap the existing cgen.nim and its friends so that
|
||||
## we call directly into the existing code generation logic but avoiding the
|
||||
## naive, outdated `passes` design. Thus you will see some
|
||||
## `useAliveDataFromDce in flags` checks in the old code -- the old code is
|
||||
## also doing cross-module dependency tracking and DCE that we don't need
|
||||
## anymore. DCE is now done as prepass over the entire packed module graph.
|
||||
|
||||
import std/[packedsets, algorithm, tables]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ".."/[ast, options, lineinfos, modulegraphs, cgendata, cgen,
|
||||
pathutils, extccomp, msgs, modulepaths]
|
||||
|
||||
import packed_ast, ic, dce, rodfiles
|
||||
|
||||
proc unpackTree(g: ModuleGraph; thisModule: int;
|
||||
tree: PackedTree; n: NodePos): PNode =
|
||||
var decoder = initPackedDecoder(g.config, g.cache)
|
||||
result = loadNodes(decoder, g.packed, thisModule, tree, n)
|
||||
|
||||
proc setupBackendModule(g: ModuleGraph; m: var LoadedModule) =
|
||||
if g.backend == nil:
|
||||
g.backend = cgendata.newModuleList(g)
|
||||
assert g.backend != nil
|
||||
var bmod = cgen.newModule(BModuleList(g.backend), m.module, g.config, idgenFromLoadedModule(m))
|
||||
|
||||
proc generateCodeForModule(g: ModuleGraph; m: var LoadedModule; alive: var AliveSyms) =
|
||||
var bmod = BModuleList(g.backend).mods[m.module.position]
|
||||
assert bmod != nil
|
||||
bmod.flags.incl useAliveDataFromDce
|
||||
bmod.alive = move alive[m.module.position]
|
||||
|
||||
for p in allNodes(m.fromDisk.topLevel):
|
||||
let n = unpackTree(g, m.module.position, m.fromDisk.topLevel, p)
|
||||
cgen.genTopLevelStmt(bmod, n)
|
||||
|
||||
finalCodegenActions(g, bmod, newNodeI(nkStmtList, m.module.info))
|
||||
for disp in getDispatchers(g):
|
||||
genProcLvl3(bmod, disp)
|
||||
m.fromDisk.backendFlags = cgen.whichInitProcs(bmod)
|
||||
|
||||
proc replayTypeInfo(g: ModuleGraph; m: var LoadedModule; origin: FileIndex) =
|
||||
for x in mitems(m.fromDisk.emittedTypeInfo):
|
||||
#echo "found type ", x, " for file ", int(origin)
|
||||
g.emittedTypeInfo[x] = origin
|
||||
|
||||
proc addFileToLink(config: ConfigRef; m: PSym) =
|
||||
let filename = AbsoluteFile toFullPath(config, m.position.FileIndex)
|
||||
let ext =
|
||||
if config.backend == backendCpp: ".nim.cpp"
|
||||
elif config.backend == backendObjc: ".nim.m"
|
||||
else: ".nim.c"
|
||||
let cfile = changeFileExt(completeCfilePath(config,
|
||||
mangleModuleName(config, filename).AbsoluteFile), ext)
|
||||
let objFile = completeCfilePath(config, toObjFile(config, cfile))
|
||||
if fileExists(objFile):
|
||||
var cf = Cfile(nimname: m.name.s, cname: cfile,
|
||||
obj: objFile,
|
||||
flags: {CfileFlag.Cached})
|
||||
addFileToCompile(config, cf)
|
||||
|
||||
when defined(debugDce):
|
||||
import os, std/packedsets
|
||||
|
||||
proc storeAliveSymsImpl(asymFile: AbsoluteFile; s: seq[int32]) =
|
||||
var f = rodfiles.create(asymFile.string)
|
||||
f.storeHeader()
|
||||
f.storeSection aliveSymsSection
|
||||
f.storeSeq(s)
|
||||
close f
|
||||
|
||||
template prepare {.dirty.} =
|
||||
let asymFile = toRodFile(config, AbsoluteFile toFullPath(config, position.FileIndex), ".alivesyms")
|
||||
var s = newSeqOfCap[int32](alive[position].len)
|
||||
for a in items(alive[position]): s.add int32(a)
|
||||
sort(s)
|
||||
|
||||
proc storeAliveSyms(config: ConfigRef; position: int; alive: AliveSyms) =
|
||||
prepare()
|
||||
storeAliveSymsImpl(asymFile, s)
|
||||
|
||||
proc aliveSymsChanged(config: ConfigRef; position: int; alive: AliveSyms): bool =
|
||||
prepare()
|
||||
var f2 = rodfiles.open(asymFile.string)
|
||||
f2.loadHeader()
|
||||
f2.loadSection aliveSymsSection
|
||||
var oldData: seq[int32] = @[]
|
||||
f2.loadSeq(oldData)
|
||||
f2.close
|
||||
if f2.err == ok and oldData == s:
|
||||
result = false
|
||||
else:
|
||||
when defined(debugDce):
|
||||
let oldAsSet = toPackedSet[int32](oldData)
|
||||
let newAsSet = toPackedSet[int32](s)
|
||||
echo "set of live symbols changed ", asymFile.changeFileExt("rod"), " ", position, " ", f2.err
|
||||
echo "in old but not in new ", oldAsSet.difference(newAsSet), " number of entries in old ", oldAsSet.len
|
||||
echo "in new but not in old ", newAsSet.difference(oldAsSet), " number of entries in new ", newAsSet.len
|
||||
#if execShellCmd(getAppFilename() & " rod " & quoteShell(asymFile.changeFileExt("rod"))) != 0:
|
||||
# echo "command failed"
|
||||
result = true
|
||||
storeAliveSymsImpl(asymFile, s)
|
||||
|
||||
proc genPackedModule(g: ModuleGraph, i: int; alive: var AliveSyms) =
|
||||
# case statement here to enforce exhaustive checks.
|
||||
case g.packed[i].status
|
||||
of undefined:
|
||||
discard "nothing to do"
|
||||
of loading, stored:
|
||||
assert false
|
||||
of storing, outdated:
|
||||
storeAliveSyms(g.config, g.packed[i].module.position, alive)
|
||||
generateCodeForModule(g, g.packed[i], alive)
|
||||
closeRodFile(g, g.packed[i].module)
|
||||
of loaded:
|
||||
if g.packed[i].loadedButAliveSetChanged:
|
||||
generateCodeForModule(g, g.packed[i], alive)
|
||||
else:
|
||||
addFileToLink(g.config, g.packed[i].module)
|
||||
replayTypeInfo(g, g.packed[i], FileIndex(i))
|
||||
|
||||
if g.backend == nil:
|
||||
g.backend = cgendata.newModuleList(g)
|
||||
registerInitProcs(BModuleList(g.backend), g.packed[i].module, g.packed[i].fromDisk.backendFlags)
|
||||
|
||||
proc generateCode*(g: ModuleGraph) =
|
||||
## The single entry point, generate C(++) code for the entire
|
||||
## Nim program aka `ModuleGraph`.
|
||||
resetForBackend(g)
|
||||
var alive = computeAliveSyms(g.packed, g.config)
|
||||
|
||||
when false:
|
||||
for i in 0..<len(g.packed):
|
||||
echo i, " is of status ", g.packed[i].status, " ", toFullPath(g.config, FileIndex(i))
|
||||
|
||||
# First pass: Setup all the backend modules for all the modules that have
|
||||
# changed:
|
||||
for i in 0..<len(g.packed):
|
||||
# case statement here to enforce exhaustive checks.
|
||||
case g.packed[i].status
|
||||
of undefined:
|
||||
discard "nothing to do"
|
||||
of loading, stored:
|
||||
assert false
|
||||
of storing, outdated:
|
||||
setupBackendModule(g, g.packed[i])
|
||||
of loaded:
|
||||
# Even though this module didn't change, DCE might trigger a change.
|
||||
# Consider this case: Module A uses symbol S from B and B does not use
|
||||
# S itself. A is then edited not to use S either. Thus we have to
|
||||
# recompile B in order to remove S from the final result.
|
||||
if aliveSymsChanged(g.config, g.packed[i].module.position, alive):
|
||||
g.packed[i].loadedButAliveSetChanged = true
|
||||
setupBackendModule(g, g.packed[i])
|
||||
|
||||
# Second pass: Code generation.
|
||||
let mainModuleIdx = g.config.projectMainIdx2.int
|
||||
# We need to generate the main module last, because only then
|
||||
# all init procs have been registered:
|
||||
for i in 0..<len(g.packed):
|
||||
if i != mainModuleIdx:
|
||||
genPackedModule(g, i, alive)
|
||||
if mainModuleIdx >= 0:
|
||||
genPackedModule(g, mainModuleIdx, alive)
|
||||
169
compiler/ic/dce.nim
Normal file
169
compiler/ic/dce.nim
Normal file
@@ -0,0 +1,169 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2021 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Dead code elimination (=DCE) for IC.
|
||||
|
||||
import std/[intsets, tables]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ".." / [ast, options, lineinfos, types]
|
||||
|
||||
import packed_ast, ic, bitabs
|
||||
|
||||
type
|
||||
AliveSyms* = seq[IntSet]
|
||||
AliveContext* = object ## Purpose is to fill the 'alive' field.
|
||||
stack: seq[(int, TOptions, NodePos)] ## A stack for marking symbols as alive.
|
||||
decoder: PackedDecoder ## We need a PackedDecoder for module ID address translations.
|
||||
thisModule: int ## The module we're currently analysing for DCE.
|
||||
alive: AliveSyms ## The final result of our computation.
|
||||
options: TOptions
|
||||
compilerProcs: Table[string, (int, int32)]
|
||||
|
||||
proc isExportedToC(c: var AliveContext; g: PackedModuleGraph; symId: int32): bool =
|
||||
## "Exported to C" procs are special (these are marked with '.exportc') because these
|
||||
## must not be optimized away!
|
||||
let symPtr = unsafeAddr g[c.thisModule].fromDisk.syms[symId]
|
||||
let flags = symPtr.flags
|
||||
# due to a bug/limitation in the lambda lifting, unused inner procs
|
||||
# are not transformed correctly; issue (#411). However, the whole purpose here
|
||||
# is to eliminate unused procs. So there is no special logic required for this case.
|
||||
if sfCompileTime notin flags:
|
||||
if ({sfExportc, sfCompilerProc} * flags != {}) or
|
||||
(symPtr.kind == skMethod):
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
# XXX: This used to be a condition to:
|
||||
# (sfExportc in prc.flags and lfExportLib in prc.loc.flags) or
|
||||
if sfCompilerProc in flags:
|
||||
c.compilerProcs[g[c.thisModule].fromDisk.strings[symPtr.name]] = (c.thisModule, symId)
|
||||
else:
|
||||
result = false
|
||||
|
||||
template isNotGeneric(n: NodePos): bool = ithSon(tree, n, genericParamsPos).kind == nkEmpty
|
||||
|
||||
proc followLater(c: var AliveContext; g: PackedModuleGraph; module: int; item: int32) =
|
||||
## Marks a symbol 'item' as used and later in 'followNow' the symbol's body will
|
||||
## be analysed.
|
||||
if not c.alive[module].containsOrIncl(item):
|
||||
var body = g[module].fromDisk.syms[item].ast
|
||||
if body != emptyNodeId:
|
||||
let opt = g[module].fromDisk.syms[item].options
|
||||
if g[module].fromDisk.syms[item].kind in routineKinds:
|
||||
body = NodeId ithSon(g[module].fromDisk.bodies, NodePos body, bodyPos)
|
||||
c.stack.add((module, opt, NodePos(body)))
|
||||
|
||||
when false:
|
||||
let nid = g[module].fromDisk.syms[item].name
|
||||
if nid != LitId(0):
|
||||
let name = g[module].fromDisk.strings[nid]
|
||||
if name in ["nimFrame", "callDepthLimitReached"]:
|
||||
echo "I was called! ", name, " body exists: ", body != emptyNodeId, " ", module, " ", item
|
||||
|
||||
proc requestCompilerProc(c: var AliveContext; g: PackedModuleGraph; name: string) =
|
||||
let (module, item) = c.compilerProcs[name]
|
||||
followLater(c, g, module, item)
|
||||
|
||||
proc loadTypeKind(t: PackedItemId; c: AliveContext; g: PackedModuleGraph; toSkip: set[TTypeKind]): TTypeKind =
|
||||
template kind(t: ItemId): TTypeKind = g[t.module].fromDisk.types[t.item].kind
|
||||
|
||||
var t2 = translateId(t, g, c.thisModule, c.decoder.config)
|
||||
result = t2.kind
|
||||
while result in toSkip:
|
||||
t2 = translateId(g[t2.module].fromDisk.types[t2.item].types[^1], g, t2.module, c.decoder.config)
|
||||
result = t2.kind
|
||||
|
||||
proc rangeCheckAnalysis(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: NodePos) =
|
||||
## Replicates the logic of `ccgexprs.genRangeChck`.
|
||||
## XXX Refactor so that the duplicated logic is avoided. However, for now it's not clear
|
||||
## the approach has enough merit.
|
||||
var dest = loadTypeKind(n.typ, c, g, abstractVar)
|
||||
if optRangeCheck notin c.options or dest in {tyUInt..tyUInt64}:
|
||||
discard "no need to generate a check because it was disabled"
|
||||
else:
|
||||
let n0t = loadTypeKind(n.firstSon.typ, c, g, {})
|
||||
if n0t in {tyUInt, tyUInt64}:
|
||||
c.requestCompilerProc(g, "raiseRangeErrorNoArgs")
|
||||
else:
|
||||
let raiser =
|
||||
case loadTypeKind(n.typ, c, g, abstractVarRange)
|
||||
of tyUInt..tyUInt64, tyChar: "raiseRangeErrorU"
|
||||
of tyFloat..tyFloat128: "raiseRangeErrorF"
|
||||
else: "raiseRangeErrorI"
|
||||
c.requestCompilerProc(g, raiser)
|
||||
|
||||
proc aliveCode(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: NodePos) =
|
||||
## Marks the symbols we encounter when we traverse the AST at `tree[n]` as alive, unless
|
||||
## it is purely in a declarative context (type section etc.).
|
||||
case n.kind
|
||||
of nkNone..pred(nkSym), succ(nkSym)..nkNilLit:
|
||||
discard "ignore non-sym atoms"
|
||||
of nkSym:
|
||||
# This symbol is alive and everything its body references.
|
||||
followLater(c, g, c.thisModule, tree[n].soperand)
|
||||
of nkModuleRef:
|
||||
let (n1, n2) = sons2(tree, n)
|
||||
assert n1.kind == nkNone
|
||||
assert n2.kind == nkNone
|
||||
let m = n1.litId
|
||||
let item = tree[n2].soperand
|
||||
let otherModule = toFileIndexCached(c.decoder, g, c.thisModule, m).int
|
||||
followLater(c, g, otherModule, item)
|
||||
of nkMacroDef, nkTemplateDef, nkTypeSection, nkTypeOfExpr,
|
||||
nkCommentStmt, nkIncludeStmt,
|
||||
nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt,
|
||||
nkFromStmt, nkStaticStmt:
|
||||
discard
|
||||
of nkVarSection, nkLetSection, nkConstSection:
|
||||
# XXX ignore the defining local variable name?
|
||||
for son in sonsReadonly(tree, n):
|
||||
aliveCode(c, g, tree, son)
|
||||
of nkChckRangeF, nkChckRange64, nkChckRange:
|
||||
rangeCheckAnalysis(c, g, tree, n)
|
||||
of nkProcDef, nkConverterDef, nkMethodDef, nkFuncDef, nkIteratorDef:
|
||||
if n.firstSon.kind == nkSym and isNotGeneric(n):
|
||||
let item = tree[n.firstSon].soperand
|
||||
if isExportedToC(c, g, item):
|
||||
# This symbol is alive and everything its body references.
|
||||
followLater(c, g, c.thisModule, item)
|
||||
else:
|
||||
for son in sonsReadonly(tree, n):
|
||||
aliveCode(c, g, tree, son)
|
||||
|
||||
proc followNow(c: var AliveContext; g: PackedModuleGraph) =
|
||||
## Mark all entries in the stack. Marking can add more entries
|
||||
## to the stack but eventually we have looked at every alive symbol.
|
||||
while c.stack.len > 0:
|
||||
let (modId, opt, ast) = c.stack.pop()
|
||||
c.thisModule = modId
|
||||
c.options = opt
|
||||
aliveCode(c, g, g[modId].fromDisk.bodies, ast)
|
||||
|
||||
proc computeAliveSyms*(g: PackedModuleGraph; conf: ConfigRef): AliveSyms =
|
||||
## Entry point for our DCE algorithm.
|
||||
var c = AliveContext(stack: @[], decoder: PackedDecoder(config: conf),
|
||||
thisModule: -1, alive: newSeq[IntSet](g.len),
|
||||
options: conf.options)
|
||||
for i in countdown(len(g)-1, 0):
|
||||
if g[i].status != undefined:
|
||||
c.thisModule = i
|
||||
for p in allNodes(g[i].fromDisk.topLevel):
|
||||
aliveCode(c, g, g[i].fromDisk.topLevel, p)
|
||||
|
||||
followNow(c, g)
|
||||
result = move(c.alive)
|
||||
|
||||
proc isAlive*(a: AliveSyms; module: int, item: int32): bool =
|
||||
## Backends use this to query if a symbol is `alive` which means
|
||||
## we need to produce (C/C++/etc) code for it.
|
||||
result = a[module].contains(item)
|
||||
|
||||
56
compiler/ic/design.rst
Normal file
56
compiler/ic/design.rst
Normal file
@@ -0,0 +1,56 @@
|
||||
====================================
|
||||
Incremental Recompilations
|
||||
====================================
|
||||
|
||||
We split the Nim compiler into a frontend and a backend.
|
||||
The frontend produces a set of `.rod` files. Every `.nim` module
|
||||
produces its own `.rod` file.
|
||||
|
||||
- The IR must be a faithful representation of the AST in memory.
|
||||
- The backend can do its own caching but doesn't have to. In the
|
||||
current implementation the backend also caches its results.
|
||||
|
||||
Advantage of the "set of files" vs the previous global database:
|
||||
- By construction, we either read from the `.rod` file or from the
|
||||
`.nim` file, there can be no inconsistency. There can also be no
|
||||
partial updates.
|
||||
- No dependency to external packages (SQLite). SQLite simply is too
|
||||
slow and the old way of serialization was too slow too. We use a
|
||||
format designed for Nim and expect to base further tools on this
|
||||
file format.
|
||||
|
||||
References to external modules must be (moduleId, symId) pairs.
|
||||
The symbol IDs are module specific. This way no global ID increment
|
||||
mechanism needs to be implemented that we could get wrong. ModuleIds
|
||||
are rod-file specific too.
|
||||
|
||||
|
||||
|
||||
Global state
|
||||
------------
|
||||
|
||||
There is no global state.
|
||||
|
||||
Rod File Format
|
||||
---------------
|
||||
|
||||
It's a simple binary file format. `rodfiles.nim` contains some details.
|
||||
|
||||
|
||||
Backend
|
||||
-------
|
||||
|
||||
Nim programmers have to come to enjoy whole-program dead code elimination,
|
||||
by default. Since this is a "whole program" optimization, it does break
|
||||
modularity. However, thanks to the packed AST representation we can perform
|
||||
this global analysis without having to unpack anything. This is basically
|
||||
a mark&sweep GC algorithm:
|
||||
|
||||
- Start with the top level statements. Every symbol that is referenced
|
||||
from a top level statement is not "dead" and needs to be compiled by
|
||||
the backend.
|
||||
- Every symbol referenced from a referenced symbol also has to be
|
||||
compiled.
|
||||
|
||||
Caching logic: Only if the set of alive symbols is different from the
|
||||
last run, the module has to be regenerated.
|
||||
1349
compiler/ic/ic.nim
Normal file
1349
compiler/ic/ic.nim
Normal file
File diff suppressed because it is too large
Load Diff
84
compiler/ic/iclineinfos.nim
Normal file
84
compiler/ic/iclineinfos.nim
Normal file
@@ -0,0 +1,84 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2024 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# For the line information we use 32 bits. They are used as follows:
|
||||
# Bit 0 (AsideBit): If we have inline line information or not. If not, the
|
||||
# remaining 31 bits are used as an index into a seq[(LitId, int, int)].
|
||||
#
|
||||
# We use 10 bits for the "file ID", this means a program can consist of as much
|
||||
# as 1024 different files. (If it uses more files than that, the overflow bit
|
||||
# would be set.)
|
||||
# This means we have 21 bits left to encode the (line, col) pair. We use 7 bits for the column
|
||||
# so 128 is the limit and 14 bits for the line number.
|
||||
# The packed representation supports files with up to 16384 lines.
|
||||
# Keep in mind that whenever any limit is reached the AsideBit is set and the real line
|
||||
# information is kept in a side channel.
|
||||
|
||||
import std / assertions
|
||||
|
||||
const
|
||||
AsideBit = 1
|
||||
FileBits = 10
|
||||
LineBits = 14
|
||||
ColBits = 7
|
||||
FileMax = (1 shl FileBits) - 1
|
||||
LineMax = (1 shl LineBits) - 1
|
||||
ColMax = (1 shl ColBits) - 1
|
||||
|
||||
static:
|
||||
assert AsideBit + FileBits + LineBits + ColBits == 32
|
||||
|
||||
import .. / ic / [bitabs, rodfiles] # for LitId
|
||||
|
||||
type
|
||||
PackedLineInfo* = distinct uint32
|
||||
|
||||
LineInfoManager* = object
|
||||
aside: seq[(LitId, int32, int32)]
|
||||
|
||||
const
|
||||
NoLineInfo* = PackedLineInfo(0'u32)
|
||||
|
||||
proc pack*(m: var LineInfoManager; file: LitId; line, col: int32): PackedLineInfo =
|
||||
if file.uint32 <= FileMax.uint32 and line <= LineMax and col <= ColMax:
|
||||
let col = if col < 0'i32: 0'u32 else: col.uint32
|
||||
let line = if line < 0'i32: 0'u32 else: line.uint32
|
||||
# use inline representation:
|
||||
result = PackedLineInfo((file.uint32 shl 1'u32) or (line shl uint32(AsideBit + FileBits)) or
|
||||
(col shl uint32(AsideBit + FileBits + LineBits)))
|
||||
else:
|
||||
result = PackedLineInfo((m.aside.len shl 1) or AsideBit)
|
||||
m.aside.add (file, line, col)
|
||||
|
||||
proc unpack*(m: LineInfoManager; i: PackedLineInfo): (LitId, int32, int32) =
|
||||
let i = i.uint32
|
||||
if (i and 1'u32) == 0'u32:
|
||||
# inline representation:
|
||||
result = (LitId((i shr 1'u32) and FileMax.uint32),
|
||||
int32((i shr uint32(AsideBit + FileBits)) and LineMax.uint32),
|
||||
int32((i shr uint32(AsideBit + FileBits + LineBits)) and ColMax.uint32))
|
||||
else:
|
||||
result = m.aside[int(i shr 1'u32)]
|
||||
|
||||
proc getFileId*(m: LineInfoManager; i: PackedLineInfo): LitId =
|
||||
result = unpack(m, i)[0]
|
||||
|
||||
proc store*(r: var RodFile; m: LineInfoManager) = storeSeq(r, m.aside)
|
||||
proc load*(r: var RodFile; m: var LineInfoManager) = loadSeq(r, m.aside)
|
||||
|
||||
when isMainModule:
|
||||
var m = LineInfoManager(aside: @[])
|
||||
for i in 0'i32..<16388'i32:
|
||||
for col in 0'i32..<100'i32:
|
||||
let packed = pack(m, LitId(1023), i, col)
|
||||
let u = unpack(m, packed)
|
||||
assert u[0] == LitId(1023)
|
||||
assert u[1] == i
|
||||
assert u[2] == col
|
||||
echo m.aside.len
|
||||
155
compiler/ic/integrity.nim
Normal file
155
compiler/ic/integrity.nim
Normal file
@@ -0,0 +1,155 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2021 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Integrity checking for a set of .rod files.
|
||||
## The set must cover a complete Nim project.
|
||||
|
||||
import std/[sets, tables]
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ".." / [ast, modulegraphs]
|
||||
import packed_ast, bitabs, ic
|
||||
|
||||
type
|
||||
CheckedContext = object
|
||||
g: ModuleGraph
|
||||
thisModule: int32
|
||||
checkedSyms: HashSet[ItemId]
|
||||
checkedTypes: HashSet[ItemId]
|
||||
|
||||
proc checkType(c: var CheckedContext; typeId: PackedItemId)
|
||||
proc checkForeignSym(c: var CheckedContext; symId: PackedItemId)
|
||||
proc checkNode(c: var CheckedContext; tree: PackedTree; n: NodePos)
|
||||
|
||||
proc checkTypeObj(c: var CheckedContext; typ: PackedType) =
|
||||
for child in typ.types:
|
||||
checkType(c, child)
|
||||
if typ.n != emptyNodeId:
|
||||
checkNode(c, c.g.packed[c.thisModule].fromDisk.bodies, NodePos typ.n)
|
||||
if typ.sym != nilItemId:
|
||||
checkForeignSym(c, typ.sym)
|
||||
if typ.owner != nilItemId:
|
||||
checkForeignSym(c, typ.owner)
|
||||
checkType(c, typ.typeInst)
|
||||
|
||||
proc checkType(c: var CheckedContext; typeId: PackedItemId) =
|
||||
if typeId == nilItemId: return
|
||||
let itemId = translateId(typeId, c.g.packed, c.thisModule, c.g.config)
|
||||
if not c.checkedTypes.containsOrIncl(itemId):
|
||||
let oldThisModule = c.thisModule
|
||||
c.thisModule = itemId.module
|
||||
checkTypeObj c, c.g.packed[itemId.module].fromDisk.types[itemId.item]
|
||||
c.thisModule = oldThisModule
|
||||
|
||||
proc checkSym(c: var CheckedContext; s: PackedSym) =
|
||||
if s.name != LitId(0):
|
||||
assert c.g.packed[c.thisModule].fromDisk.strings.hasLitId s.name
|
||||
checkType c, s.typ
|
||||
if s.ast != emptyNodeId:
|
||||
checkNode(c, c.g.packed[c.thisModule].fromDisk.bodies, NodePos s.ast)
|
||||
if s.owner != nilItemId:
|
||||
checkForeignSym(c, s.owner)
|
||||
|
||||
proc checkLocalSym(c: var CheckedContext; item: int32) =
|
||||
let itemId = ItemId(module: c.thisModule, item: item)
|
||||
if not c.checkedSyms.containsOrIncl(itemId):
|
||||
checkSym c, c.g.packed[c.thisModule].fromDisk.syms[item]
|
||||
|
||||
proc checkForeignSym(c: var CheckedContext; symId: PackedItemId) =
|
||||
let itemId = translateId(symId, c.g.packed, c.thisModule, c.g.config)
|
||||
if not c.checkedSyms.containsOrIncl(itemId):
|
||||
let oldThisModule = c.thisModule
|
||||
c.thisModule = itemId.module
|
||||
checkSym c, c.g.packed[itemId.module].fromDisk.syms[itemId.item]
|
||||
c.thisModule = oldThisModule
|
||||
|
||||
proc checkNode(c: var CheckedContext; tree: PackedTree; n: NodePos) =
|
||||
let t = findType(tree, n)
|
||||
if t != nilItemId:
|
||||
checkType(c, t)
|
||||
case n.kind
|
||||
of nkEmpty, nkNilLit, nkType, nkNilRodNode:
|
||||
discard
|
||||
of nkIdent:
|
||||
assert c.g.packed[c.thisModule].fromDisk.strings.hasLitId n.litId
|
||||
of nkSym:
|
||||
checkLocalSym(c, tree[n].soperand)
|
||||
of directIntLit:
|
||||
discard
|
||||
of externIntLit, nkFloatLit..nkFloat128Lit:
|
||||
assert c.g.packed[c.thisModule].fromDisk.numbers.hasLitId n.litId
|
||||
of nkStrLit..nkTripleStrLit:
|
||||
assert c.g.packed[c.thisModule].fromDisk.strings.hasLitId n.litId
|
||||
of nkModuleRef:
|
||||
let (n1, n2) = sons2(tree, n)
|
||||
assert n1.kind == nkNone
|
||||
assert n2.kind == nkNone
|
||||
checkForeignSym(c, PackedItemId(module: n1.litId, item: tree[n2].soperand))
|
||||
else:
|
||||
for n0 in sonsReadonly(tree, n):
|
||||
checkNode(c, tree, n0)
|
||||
|
||||
proc checkTree(c: var CheckedContext; t: PackedTree) =
|
||||
for p in allNodes(t): checkNode(c, t, p)
|
||||
|
||||
proc checkLocalSymIds(c: var CheckedContext; m: PackedModule; symIds: seq[int32]) =
|
||||
for symId in symIds:
|
||||
assert symId >= 0 and symId < m.syms.len, $symId & " " & $m.syms.len
|
||||
|
||||
proc checkModule(c: var CheckedContext; m: PackedModule) =
|
||||
# We check that:
|
||||
# - Every symbol references existing types and symbols.
|
||||
# - Every tree node references existing types and symbols.
|
||||
for _, v in pairs(m.syms):
|
||||
checkLocalSym c, v.id
|
||||
|
||||
checkTree c, m.toReplay
|
||||
checkTree c, m.topLevel
|
||||
|
||||
for e in m.exports:
|
||||
#assert e[1] >= 0 and e[1] < m.syms.len
|
||||
assert e[0] == m.syms[e[1]].name
|
||||
|
||||
for e in m.compilerProcs:
|
||||
#assert e[1] >= 0 and e[1] < m.syms.len
|
||||
assert e[0] == m.syms[e[1]].name
|
||||
|
||||
checkLocalSymIds c, m, m.converters
|
||||
checkLocalSymIds c, m, m.methods
|
||||
checkLocalSymIds c, m, m.trmacros
|
||||
checkLocalSymIds c, m, m.pureEnums
|
||||
#[
|
||||
To do: Check all these fields:
|
||||
|
||||
reexports*: seq[(LitId, PackedItemId)]
|
||||
macroUsages*: seq[(PackedItemId, PackedLineInfo)]
|
||||
|
||||
typeInstCache*: seq[(PackedItemId, PackedItemId)]
|
||||
procInstCache*: seq[PackedInstantiation]
|
||||
attachedOps*: seq[(TTypeAttachedOp, PackedItemId, PackedItemId)]
|
||||
methodsPerGenericType*: seq[(PackedItemId, int, PackedItemId)]
|
||||
enumToStringProcs*: seq[(PackedItemId, PackedItemId)]
|
||||
methodsPerType*: seq[(PackedItemId, PackedItemId)]
|
||||
dispatchers*: seq[PackedItemId]
|
||||
]#
|
||||
|
||||
proc checkIntegrity*(g: ModuleGraph) =
|
||||
var c = CheckedContext(g: g)
|
||||
for i in 0..<len(g.packed):
|
||||
# case statement here to enforce exhaustive checks.
|
||||
case g.packed[i].status
|
||||
of undefined:
|
||||
discard "nothing to do"
|
||||
of loading:
|
||||
assert false, "cannot check integrity: Module still loading"
|
||||
of stored, storing, outdated, loaded:
|
||||
c.thisModule = int32 i
|
||||
checkModule(c, g.packed[i].fromDisk)
|
||||
183
compiler/ic/navigator.nim
Normal file
183
compiler/ic/navigator.nim
Normal file
@@ -0,0 +1,183 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2021 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Supports the "nim check --ic:legacy --defusages:FILE,LINE,COL"
|
||||
## IDE-like features. It uses the set of .rod files to accomplish
|
||||
## its task. The set must cover a complete Nim project.
|
||||
|
||||
import std/[sets, tables]
|
||||
|
||||
from std/os import nil
|
||||
from std/private/miscdollars import toLocation
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import ".." / [ast, modulegraphs, msgs, options]
|
||||
import iclineinfos
|
||||
import packed_ast, bitabs, ic
|
||||
|
||||
type
|
||||
UnpackedLineInfo = object
|
||||
file: LitId
|
||||
line, col: int
|
||||
NavContext = object
|
||||
g: ModuleGraph
|
||||
thisModule: int32
|
||||
trackPos: UnpackedLineInfo
|
||||
alreadyEmitted: HashSet[string]
|
||||
outputSep: char # for easier testing, use short filenames and spaces instead of tabs.
|
||||
|
||||
proc isTracked(man: LineInfoManager; current: PackedLineInfo, trackPos: UnpackedLineInfo, tokenLen: int): bool =
|
||||
let (currentFile, currentLine, currentCol) = man.unpack(current)
|
||||
if currentFile == trackPos.file and currentLine == trackPos.line:
|
||||
let col = trackPos.col
|
||||
if col >= currentCol and col < currentCol+tokenLen:
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc searchLocalSym(c: var NavContext; s: PackedSym; info: PackedLineInfo): bool =
|
||||
result = s.name != LitId(0) and
|
||||
isTracked(c.g.packed[c.thisModule].fromDisk.man, info, c.trackPos, c.g.packed[c.thisModule].fromDisk.strings[s.name].len)
|
||||
|
||||
proc searchForeignSym(c: var NavContext; s: ItemId; info: PackedLineInfo): bool =
|
||||
let name = c.g.packed[s.module].fromDisk.syms[s.item].name
|
||||
result = name != LitId(0) and
|
||||
isTracked(c.g.packed[c.thisModule].fromDisk.man, info, c.trackPos, c.g.packed[s.module].fromDisk.strings[name].len)
|
||||
|
||||
const
|
||||
EmptyItemId = ItemId(module: -1'i32, item: -1'i32)
|
||||
|
||||
proc search(c: var NavContext; tree: PackedTree): ItemId =
|
||||
# We use the linear representation here directly:
|
||||
for i in 0..<len(tree):
|
||||
let i = NodePos(i)
|
||||
case tree[i].kind
|
||||
of nkSym:
|
||||
let item = tree[i].soperand
|
||||
if searchLocalSym(c, c.g.packed[c.thisModule].fromDisk.syms[item], tree[i].info):
|
||||
return ItemId(module: c.thisModule, item: item)
|
||||
of nkModuleRef:
|
||||
let (currentFile, currentLine, currentCol) = c.g.packed[c.thisModule].fromDisk.man.unpack(tree[i].info)
|
||||
if currentLine == c.trackPos.line and currentFile == c.trackPos.file:
|
||||
let (n1, n2) = sons2(tree, i)
|
||||
assert n1.kind == nkInt32Lit
|
||||
assert n2.kind == nkInt32Lit
|
||||
let pId = PackedItemId(module: n1.litId, item: tree[n2].soperand)
|
||||
let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
|
||||
if searchForeignSym(c, itemId, tree[i].info):
|
||||
return itemId
|
||||
else: discard
|
||||
return EmptyItemId
|
||||
|
||||
proc isDecl(tree: PackedTree; n: NodePos): bool =
|
||||
# XXX This is not correct yet.
|
||||
const declarativeNodes = procDefs + {nkMacroDef, nkTemplateDef,
|
||||
nkLetSection, nkVarSection, nkUsingStmt, nkConstSection, nkTypeSection,
|
||||
nkIdentDefs, nkEnumTy, nkVarTuple}
|
||||
result = n.int >= 0 and tree[n].kind in declarativeNodes
|
||||
|
||||
proc usage(c: var NavContext; info: PackedLineInfo; isDecl: bool) =
|
||||
let (fileId, line, col) = unpack(c.g.packed[c.thisModule].fromDisk.man, info)
|
||||
var m = ""
|
||||
var file = c.g.packed[c.thisModule].fromDisk.strings[fileId]
|
||||
if c.outputSep == ' ':
|
||||
file = os.extractFilename file
|
||||
toLocation(m, file, line, col + ColOffset)
|
||||
if not c.alreadyEmitted.containsOrIncl(m):
|
||||
msgWriteln c.g.config, (if isDecl: "def" else: "usage") & c.outputSep & m
|
||||
|
||||
proc list(c: var NavContext; tree: PackedTree; sym: ItemId) =
|
||||
for i in 0..<len(tree):
|
||||
let i = NodePos(i)
|
||||
case tree[i].kind
|
||||
of nkSym:
|
||||
let item = tree[i].soperand
|
||||
if sym.item == item and sym.module == c.thisModule:
|
||||
usage(c, tree[i].info, isDecl(tree, parent(i)))
|
||||
of nkModuleRef:
|
||||
let (n1, n2) = sons2(tree, i)
|
||||
assert n1.kind == nkNone
|
||||
assert n2.kind == nkNone
|
||||
let pId = PackedItemId(module: n1.litId, item: tree[n2].soperand)
|
||||
let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
|
||||
if itemId.item == sym.item and sym.module == itemId.module:
|
||||
usage(c, tree[i].info, isDecl(tree, parent(i)))
|
||||
else: discard
|
||||
|
||||
proc searchForIncludeFile(g: ModuleGraph; fullPath: string): int =
|
||||
for i in 0..<len(g.packed):
|
||||
for k in 1..high(g.packed[i].fromDisk.includes):
|
||||
# we start from 1 because the first "include" file is
|
||||
# the module's filename.
|
||||
if os.cmpPaths(g.packed[i].fromDisk.strings[g.packed[i].fromDisk.includes[k][0]], fullPath) == 0:
|
||||
return i
|
||||
return -1
|
||||
|
||||
proc nav(g: ModuleGraph) =
|
||||
# translate the track position to a packed position:
|
||||
let unpacked = g.config.m.trackPos
|
||||
var mid = unpacked.fileIndex.int
|
||||
|
||||
let fullPath = toFullPath(g.config, unpacked.fileIndex)
|
||||
|
||||
if g.packed[mid].status == undefined:
|
||||
# check if 'mid' is an include file of some other module:
|
||||
mid = searchForIncludeFile(g, fullPath)
|
||||
|
||||
if mid < 0:
|
||||
localError(g.config, unpacked, "unknown file name: " & fullPath)
|
||||
return
|
||||
|
||||
let fileId = g.packed[mid].fromDisk.strings.getKeyId(fullPath)
|
||||
|
||||
if fileId == LitId(0):
|
||||
internalError(g.config, unpacked, "cannot find a valid file ID")
|
||||
return
|
||||
|
||||
var c = NavContext(
|
||||
g: g,
|
||||
thisModule: int32 mid,
|
||||
trackPos: UnpackedLineInfo(line: unpacked.line.int, col: unpacked.col.int, file: fileId),
|
||||
outputSep: if isDefined(g.config, "nimIcNavigatorTests"): ' ' else: '\t'
|
||||
)
|
||||
var symId = search(c, g.packed[mid].fromDisk.topLevel)
|
||||
if symId == EmptyItemId:
|
||||
symId = search(c, g.packed[mid].fromDisk.bodies)
|
||||
|
||||
if symId == EmptyItemId:
|
||||
localError(g.config, unpacked, "no symbol at this position")
|
||||
return
|
||||
|
||||
for i in 0..<len(g.packed):
|
||||
# case statement here to enforce exhaustive checks.
|
||||
case g.packed[i].status
|
||||
of undefined:
|
||||
discard "nothing to do"
|
||||
of loading:
|
||||
assert false, "cannot check integrity: Module still loading"
|
||||
of stored, storing, outdated, loaded:
|
||||
c.thisModule = int32 i
|
||||
list(c, g.packed[i].fromDisk.topLevel, symId)
|
||||
list(c, g.packed[i].fromDisk.bodies, symId)
|
||||
|
||||
proc navDefinition*(g: ModuleGraph) = nav(g)
|
||||
proc navUsages*(g: ModuleGraph) = nav(g)
|
||||
proc navDefusages*(g: ModuleGraph) = nav(g)
|
||||
|
||||
proc writeRodFiles*(g: ModuleGraph) =
|
||||
for i in 0..<len(g.packed):
|
||||
case g.packed[i].status
|
||||
of undefined, loading, stored, loaded:
|
||||
discard "nothing to do"
|
||||
of storing, outdated:
|
||||
closeRodFile(g, g.packed[i].module)
|
||||
367
compiler/ic/packed_ast.nim
Normal file
367
compiler/ic/packed_ast.nim
Normal file
@@ -0,0 +1,367 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2020 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Packed AST representation, mostly based on a seq of nodes.
|
||||
## For IC support. Far future: Rewrite the compiler passes to
|
||||
## use this representation directly in all the transformations,
|
||||
## it is superior.
|
||||
|
||||
import std/[hashes, tables, strtabs]
|
||||
import bitabs, rodfiles
|
||||
import ".." / [ast, options]
|
||||
|
||||
import iclineinfos
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
type
|
||||
SymId* = distinct int32
|
||||
ModuleId* = distinct int32
|
||||
NodePos* = distinct int
|
||||
|
||||
NodeId* = distinct int32
|
||||
|
||||
PackedItemId* = object
|
||||
module*: LitId # 0 if it's this module
|
||||
item*: int32 # same as the in-memory representation
|
||||
|
||||
const
|
||||
nilItemId* = PackedItemId(module: LitId(0), item: 0.int32)
|
||||
|
||||
const
|
||||
emptyNodeId* = NodeId(-1)
|
||||
|
||||
type
|
||||
PackedLib* = object
|
||||
kind*: TLibKind
|
||||
generated*: bool
|
||||
isOverridden*: bool
|
||||
name*: LitId
|
||||
path*: NodeId
|
||||
|
||||
PackedSym* = object
|
||||
id*: int32
|
||||
kind*: TSymKind
|
||||
name*: LitId
|
||||
typ*: PackedItemId
|
||||
flags*: TSymFlags
|
||||
magic*: TMagic
|
||||
info*: PackedLineInfo
|
||||
ast*: NodeId
|
||||
owner*: PackedItemId
|
||||
guard*: PackedItemId
|
||||
bitsize*: int
|
||||
alignment*: int # for alignment
|
||||
options*: TOptions
|
||||
position*: int
|
||||
offset*: int32
|
||||
disamb*: int32
|
||||
externalName*: LitId # instead of TLoc
|
||||
locFlags*: TLocFlags
|
||||
annex*: PackedLib
|
||||
when hasFFI:
|
||||
cname*: LitId
|
||||
constraint*: NodeId
|
||||
instantiatedFrom*: PackedItemId
|
||||
|
||||
PackedType* = object
|
||||
id*: int32
|
||||
kind*: TTypeKind
|
||||
callConv*: TCallingConvention
|
||||
#nodekind*: TNodeKind
|
||||
flags*: TTypeFlags
|
||||
types*: seq[PackedItemId]
|
||||
n*: NodeId
|
||||
#nodeflags*: TNodeFlags
|
||||
sym*: PackedItemId
|
||||
owner*: PackedItemId
|
||||
size*: BiggestInt
|
||||
align*: int16
|
||||
paddingAtEnd*: int16
|
||||
# not serialized: loc*: TLoc because it is backend-specific
|
||||
typeInst*: PackedItemId
|
||||
nonUniqueId*: int32
|
||||
|
||||
PackedNode* = object # 8 bytes
|
||||
x: uint32
|
||||
info*: PackedLineInfo
|
||||
|
||||
PackedTree* = object ## usually represents a full Nim module
|
||||
nodes: seq[PackedNode]
|
||||
withFlags: seq[(int32, TNodeFlags)]
|
||||
withTypes: seq[(int32, PackedItemId)]
|
||||
|
||||
PackedInstantiation* = object
|
||||
key*, sym*: PackedItemId
|
||||
concreteTypes*: seq[PackedItemId]
|
||||
|
||||
const
|
||||
NodeKindBits = 8'u32
|
||||
NodeKindMask = (1'u32 shl NodeKindBits) - 1'u32
|
||||
|
||||
template kind*(n: PackedNode): TNodeKind = TNodeKind(n.x and NodeKindMask)
|
||||
template uoperand*(n: PackedNode): uint32 = (n.x shr NodeKindBits)
|
||||
template soperand*(n: PackedNode): int32 = int32(uoperand(n))
|
||||
|
||||
template toX(k: TNodeKind; operand: uint32): uint32 =
|
||||
uint32(k) or (operand shl NodeKindBits)
|
||||
|
||||
template toX(k: TNodeKind; operand: LitId): uint32 =
|
||||
uint32(k) or (operand.uint32 shl NodeKindBits)
|
||||
|
||||
template typeId*(n: PackedNode): PackedItemId = n.typ
|
||||
|
||||
proc `==`*(a, b: SymId): bool {.borrow.}
|
||||
proc hash*(a: SymId): Hash {.borrow.}
|
||||
|
||||
proc `==`*(a, b: NodePos): bool {.borrow.}
|
||||
#proc `==`*(a, b: PackedItemId): bool {.borrow.}
|
||||
proc `==`*(a, b: NodeId): bool {.borrow.}
|
||||
|
||||
proc newTreeFrom*(old: PackedTree): PackedTree =
|
||||
result = PackedTree(nodes: @[])
|
||||
when false: result.sh = old.sh
|
||||
|
||||
proc addIdent*(tree: var PackedTree; s: LitId; info: PackedLineInfo) =
|
||||
tree.nodes.add PackedNode(x: toX(nkIdent, uint32(s)), info: info)
|
||||
|
||||
proc addSym*(tree: var PackedTree; s: int32; info: PackedLineInfo) =
|
||||
tree.nodes.add PackedNode(x: toX(nkSym, cast[uint32](s)), info: info)
|
||||
|
||||
proc addSymDef*(tree: var PackedTree; s: SymId; info: PackedLineInfo) =
|
||||
tree.nodes.add PackedNode(x: toX(nkSym, cast[uint32](s)), info: info)
|
||||
|
||||
proc isAtom*(tree: PackedTree; pos: int): bool {.inline.} = tree.nodes[pos].kind <= nkNilLit
|
||||
|
||||
type
|
||||
PatchPos = distinct int
|
||||
|
||||
proc addNode*(t: var PackedTree; kind: TNodeKind; operand: int32;
|
||||
typeId: PackedItemId = nilItemId; info: PackedLineInfo;
|
||||
flags: TNodeFlags = {}) =
|
||||
t.nodes.add PackedNode(x: toX(kind, cast[uint32](operand)), info: info)
|
||||
if flags != {}:
|
||||
t.withFlags.add (t.nodes.len.int32 - 1, flags)
|
||||
if typeId != nilItemId:
|
||||
t.withTypes.add (t.nodes.len.int32 - 1, typeId)
|
||||
|
||||
proc prepare*(tree: var PackedTree; kind: TNodeKind; flags: TNodeFlags; typeId: PackedItemId; info: PackedLineInfo): PatchPos =
|
||||
result = PatchPos tree.nodes.len
|
||||
tree.addNode(kind = kind, flags = flags, operand = 0, info = info, typeId = typeId)
|
||||
|
||||
proc prepare*(dest: var PackedTree; source: PackedTree; sourcePos: NodePos): PatchPos =
|
||||
result = PatchPos dest.nodes.len
|
||||
dest.nodes.add source.nodes[sourcePos.int]
|
||||
|
||||
proc patch*(tree: var PackedTree; pos: PatchPos) =
|
||||
let pos = pos.int
|
||||
let k = tree.nodes[pos].kind
|
||||
assert k > nkNilLit
|
||||
let distance = int32(tree.nodes.len - pos)
|
||||
assert distance > 0
|
||||
tree.nodes[pos].x = toX(k, cast[uint32](distance))
|
||||
|
||||
proc len*(tree: PackedTree): int {.inline.} = tree.nodes.len
|
||||
|
||||
proc `[]`*(tree: PackedTree; i: NodePos): lent PackedNode {.inline.} =
|
||||
tree.nodes[i.int]
|
||||
|
||||
template rawSpan(n: PackedNode): int = int(uoperand(n))
|
||||
|
||||
proc nextChild(tree: PackedTree; pos: var int) {.inline.} =
|
||||
if tree.nodes[pos].kind > nkNilLit:
|
||||
assert tree.nodes[pos].uoperand > 0
|
||||
inc pos, tree.nodes[pos].rawSpan
|
||||
else:
|
||||
inc pos
|
||||
|
||||
iterator sonsReadonly*(tree: PackedTree; n: NodePos): NodePos =
|
||||
var pos = n.int
|
||||
assert tree.nodes[pos].kind > nkNilLit
|
||||
let last = pos + tree.nodes[pos].rawSpan
|
||||
inc pos
|
||||
while pos < last:
|
||||
yield NodePos pos
|
||||
nextChild tree, pos
|
||||
|
||||
iterator sons*(dest: var PackedTree; tree: PackedTree; n: NodePos): NodePos =
|
||||
let patchPos = prepare(dest, tree, n)
|
||||
for x in sonsReadonly(tree, n): yield x
|
||||
patch dest, patchPos
|
||||
|
||||
iterator isons*(dest: var PackedTree; tree: PackedTree;
|
||||
n: NodePos): (int, NodePos) =
|
||||
var i = 0
|
||||
for ch0 in sons(dest, tree, n):
|
||||
yield (i, ch0)
|
||||
inc i
|
||||
|
||||
iterator sonsFrom1*(tree: PackedTree; n: NodePos): NodePos =
|
||||
var pos = n.int
|
||||
assert tree.nodes[pos].kind > nkNilLit
|
||||
let last = pos + tree.nodes[pos].rawSpan
|
||||
inc pos
|
||||
if pos < last:
|
||||
nextChild tree, pos
|
||||
while pos < last:
|
||||
yield NodePos pos
|
||||
nextChild tree, pos
|
||||
|
||||
iterator sonsWithoutLast2*(tree: PackedTree; n: NodePos): NodePos =
|
||||
var count = 0
|
||||
for child in sonsReadonly(tree, n):
|
||||
inc count
|
||||
var pos = n.int
|
||||
assert tree.nodes[pos].kind > nkNilLit
|
||||
let last = pos + tree.nodes[pos].rawSpan
|
||||
inc pos
|
||||
while pos < last and count > 2:
|
||||
yield NodePos pos
|
||||
dec count
|
||||
nextChild tree, pos
|
||||
|
||||
proc parentImpl(tree: PackedTree; n: NodePos): NodePos =
|
||||
# finding the parent of a node is rather easy:
|
||||
var pos = n.int - 1
|
||||
while pos >= 0 and (isAtom(tree, pos) or (pos + tree.nodes[pos].rawSpan - 1 < n.int)):
|
||||
dec pos
|
||||
#assert pos >= 0, "node has no parent"
|
||||
result = NodePos(pos)
|
||||
|
||||
template parent*(n: NodePos): NodePos = parentImpl(tree, n)
|
||||
|
||||
proc hasXsons*(tree: PackedTree; n: NodePos; x: int): bool =
|
||||
var count = 0
|
||||
if tree.nodes[n.int].kind > nkNilLit:
|
||||
for child in sonsReadonly(tree, n): inc count
|
||||
result = count == x
|
||||
|
||||
proc hasAtLeastXsons*(tree: PackedTree; n: NodePos; x: int): bool =
|
||||
if tree.nodes[n.int].kind > nkNilLit:
|
||||
var count = 0
|
||||
for child in sonsReadonly(tree, n):
|
||||
inc count
|
||||
if count >= x: return true
|
||||
return false
|
||||
|
||||
proc firstSon*(tree: PackedTree; n: NodePos): NodePos {.inline.} =
|
||||
NodePos(n.int+1)
|
||||
proc kind*(tree: PackedTree; n: NodePos): TNodeKind {.inline.} =
|
||||
tree.nodes[n.int].kind
|
||||
proc litId*(tree: PackedTree; n: NodePos): LitId {.inline.} =
|
||||
LitId tree.nodes[n.int].uoperand
|
||||
proc info*(tree: PackedTree; n: NodePos): PackedLineInfo {.inline.} =
|
||||
tree.nodes[n.int].info
|
||||
|
||||
proc findType*(tree: PackedTree; n: NodePos): PackedItemId =
|
||||
for x in tree.withTypes:
|
||||
if x[0] == int32(n): return x[1]
|
||||
if x[0] > int32(n): return nilItemId
|
||||
return nilItemId
|
||||
|
||||
proc findFlags*(tree: PackedTree; n: NodePos): TNodeFlags =
|
||||
for x in tree.withFlags:
|
||||
if x[0] == int32(n): return x[1]
|
||||
if x[0] > int32(n): return {}
|
||||
return {}
|
||||
|
||||
template typ*(n: NodePos): PackedItemId =
|
||||
tree.findType(n)
|
||||
template flags*(n: NodePos): TNodeFlags =
|
||||
tree.findFlags(n)
|
||||
|
||||
template uoperand*(n: NodePos): uint32 =
|
||||
tree.nodes[n.int].uoperand
|
||||
|
||||
proc span*(tree: PackedTree; pos: int): int {.inline.} =
|
||||
if isAtom(tree, pos): 1 else: tree.nodes[pos].rawSpan
|
||||
|
||||
proc sons2*(tree: PackedTree; n: NodePos): (NodePos, NodePos) =
|
||||
assert(not isAtom(tree, n.int))
|
||||
let a = n.int+1
|
||||
let b = a + span(tree, a)
|
||||
result = (NodePos a, NodePos b)
|
||||
|
||||
proc sons3*(tree: PackedTree; n: NodePos): (NodePos, NodePos, NodePos) =
|
||||
assert(not isAtom(tree, n.int))
|
||||
let a = n.int+1
|
||||
let b = a + span(tree, a)
|
||||
let c = b + span(tree, b)
|
||||
result = (NodePos a, NodePos b, NodePos c)
|
||||
|
||||
proc ithSon*(tree: PackedTree; n: NodePos; i: int): NodePos =
|
||||
result = default(NodePos)
|
||||
if tree.nodes[n.int].kind > nkNilLit:
|
||||
var count = 0
|
||||
for child in sonsReadonly(tree, n):
|
||||
if count == i: return child
|
||||
inc count
|
||||
assert false, "node has no i-th child"
|
||||
|
||||
when false:
|
||||
proc `@`*(tree: PackedTree; lit: LitId): lent string {.inline.} =
|
||||
tree.sh.strings[lit]
|
||||
|
||||
template kind*(n: NodePos): TNodeKind = tree.nodes[n.int].kind
|
||||
template info*(n: NodePos): PackedLineInfo = tree.nodes[n.int].info
|
||||
template litId*(n: NodePos): LitId = LitId tree.nodes[n.int].uoperand
|
||||
|
||||
template symId*(n: NodePos): SymId = SymId tree.nodes[n.int].soperand
|
||||
|
||||
proc firstSon*(n: NodePos): NodePos {.inline.} = NodePos(n.int+1)
|
||||
|
||||
const
|
||||
externIntLit* = {nkCharLit,
|
||||
nkIntLit,
|
||||
nkInt8Lit,
|
||||
nkInt16Lit,
|
||||
nkInt32Lit,
|
||||
nkInt64Lit,
|
||||
nkUIntLit,
|
||||
nkUInt8Lit,
|
||||
nkUInt16Lit,
|
||||
nkUInt32Lit,
|
||||
nkUInt64Lit}
|
||||
|
||||
externSIntLit* = {nkIntLit, nkInt8Lit, nkInt16Lit, nkInt32Lit, nkInt64Lit}
|
||||
externUIntLit* = {nkUIntLit, nkUInt8Lit, nkUInt16Lit, nkUInt32Lit, nkUInt64Lit}
|
||||
directIntLit* = nkNone
|
||||
|
||||
template copyInto*(dest, n, body) =
|
||||
let patchPos = prepare(dest, tree, n)
|
||||
body
|
||||
patch dest, patchPos
|
||||
|
||||
template copyIntoKind*(dest, kind, info, body) =
|
||||
let patchPos = prepare(dest, kind, info)
|
||||
body
|
||||
patch dest, patchPos
|
||||
|
||||
proc getNodeId*(tree: PackedTree): NodeId {.inline.} = NodeId tree.nodes.len
|
||||
|
||||
iterator allNodes*(tree: PackedTree): NodePos =
|
||||
var p = 0
|
||||
while p < tree.len:
|
||||
yield NodePos(p)
|
||||
let s = span(tree, p)
|
||||
inc p, s
|
||||
|
||||
proc toPackedItemId*(item: int32): PackedItemId {.inline.} =
|
||||
PackedItemId(module: LitId(0), item: item)
|
||||
|
||||
proc load*(f: var RodFile; t: var PackedTree) =
|
||||
loadSeq f, t.nodes
|
||||
loadSeq f, t.withFlags
|
||||
loadSeq f, t.withTypes
|
||||
|
||||
proc store*(f: var RodFile; t: PackedTree) =
|
||||
storeSeq f, t.nodes
|
||||
storeSeq f, t.withFlags
|
||||
storeSeq f, t.withTypes
|
||||
@@ -19,6 +19,8 @@ import std/tables
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
import packed_ast, ic, bitabs
|
||||
|
||||
proc replayStateChanges*(module: PSym; g: ModuleGraph) =
|
||||
let list = module.ast
|
||||
assert list != nil
|
||||
@@ -86,3 +88,84 @@ proc replayStateChanges*(module: PSym; g: ModuleGraph) =
|
||||
g.cacheSeqs[destKey].add val
|
||||
else:
|
||||
internalAssert g.config, false
|
||||
|
||||
proc replayBackendProcs*(g: ModuleGraph; module: int) =
|
||||
for it in mitems(g.packed[module].fromDisk.attachedOps):
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
let op = it[1]
|
||||
let tmp = translateId(it[2], g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it[2])
|
||||
g.attachedOps[op][key] = LazySym(id: symId, sym: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.enumToStringProcs):
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
let tmp = translateId(it[1], g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it[1])
|
||||
g.enumToStringProcs[key] = LazySym(id: symId, sym: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.methodsPerType):
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
let tmp = translateId(it[1], g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it[1])
|
||||
g.methodsPerType.mgetOrPut(key, @[]).add LazySym(id: symId, sym: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.dispatchers):
|
||||
let tmp = translateId(it, g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it)
|
||||
g.dispatchers.add LazySym(id: symId, sym: nil)
|
||||
|
||||
proc replayGenericCacheInformation*(g: ModuleGraph; module: int) =
|
||||
## We remember the generic instantiations a module performed
|
||||
## in order to to avoid the code bloat that generic code tends
|
||||
## to imply. This is cheaper than deduplication of identical
|
||||
## generic instantiations. However, deduplication is more
|
||||
## powerful and general and I hope to implement it soon too
|
||||
## (famous last words).
|
||||
assert g.packed[module].status == loaded
|
||||
for it in g.packed[module].fromDisk.typeInstCache:
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
g.typeInstCache.mgetOrPut(key, @[]).add LazyType(id: FullId(module: module, packed: it[1]), typ: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.procInstCache):
|
||||
let key = translateId(it.key, g.packed, module, g.config)
|
||||
let sym = translateId(it.sym, g.packed, module, g.config)
|
||||
var concreteTypes = newSeq[FullId](it.concreteTypes.len)
|
||||
for i in 0..high(it.concreteTypes):
|
||||
let tmp = translateId(it.concreteTypes[i], g.packed, module, g.config)
|
||||
concreteTypes[i] = FullId(module: tmp.module, packed: it.concreteTypes[i])
|
||||
|
||||
g.procInstCache.mgetOrPut(key, @[]).add LazyInstantiation(
|
||||
module: module, sym: FullId(module: sym.module, packed: it.sym),
|
||||
concreteTypes: concreteTypes, inst: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.methodsPerGenericType):
|
||||
let key = translateId(it[0], g.packed, module, g.config)
|
||||
let col = it[1]
|
||||
let tmp = translateId(it[2], g.packed, module, g.config)
|
||||
let symId = FullId(module: tmp.module, packed: it[2])
|
||||
g.methodsPerGenericType.mgetOrPut(key, @[]).add (col, LazySym(id: symId, sym: nil))
|
||||
|
||||
replayBackendProcs(g, module)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.methods):
|
||||
let sym = loadSymFromId(g.config, g.cache, g.packed, module,
|
||||
PackedItemId(module: LitId(0), item: it))
|
||||
methodDef(g, g.idgen, sym)
|
||||
|
||||
when false:
|
||||
# not used anymore:
|
||||
for it in mitems(g.packed[module].fromDisk.compilerProcs):
|
||||
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it[1]))
|
||||
g.lazyCompilerprocs[g.packed[module].fromDisk.sh.strings[it[0]]] = symId
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.converters):
|
||||
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
|
||||
g.ifaces[module].converters.add LazySym(id: symId, sym: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.trmacros):
|
||||
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
|
||||
g.ifaces[module].patterns.add LazySym(id: symId, sym: nil)
|
||||
|
||||
for it in mitems(g.packed[module].fromDisk.pureEnums):
|
||||
let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
|
||||
g.ifaces[module].pureEnums.add LazySym(id: symId, sym: nil)
|
||||
|
||||
283
compiler/ic/rodfiles.nim
Normal file
283
compiler/ic/rodfiles.nim
Normal file
@@ -0,0 +1,283 @@
|
||||
#
|
||||
#
|
||||
# The Nim Compiler
|
||||
# (c) Copyright 2020 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Low level binary format used by the compiler to store and load various AST
|
||||
## and related data.
|
||||
##
|
||||
## NB: this is incredibly low level and if you're interested in how the
|
||||
## compiler works and less a storage format, you're probably looking for
|
||||
## the `ic` or `packed_ast` modules to understand the logical format.
|
||||
|
||||
from std/typetraits import supportsCopyMem
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/[syncio, assertions]
|
||||
|
||||
import std / tables
|
||||
|
||||
## Overview
|
||||
## ========
|
||||
## `RodFile` represents a Rod File (versioned binary format), and the
|
||||
## associated data for common interactions such as IO and error tracking
|
||||
## (`RodFileError`). The file format broken up into sections (`RodSection`)
|
||||
## and preceded by a header (see: `cookie`). The precise layout, section
|
||||
## ordering and data following the section are determined by the user. See
|
||||
## `ic.loadRodFile`.
|
||||
##
|
||||
## A basic but "wrong" example of the lifecycle:
|
||||
## ---------------------------------------------
|
||||
## 1. `create` or `open` - create a new one or open an existing
|
||||
## 2. `storeHeader` - header info
|
||||
## 3. `storePrim` or `storeSeq` - save your stuff
|
||||
## 4. `close` - and we're done
|
||||
##
|
||||
## Now read the bits below to understand what's missing.
|
||||
##
|
||||
## ### Issues with the Example
|
||||
## Missing Sections:
|
||||
## This is a low level API, so headers and sections need to be stored and
|
||||
## loaded by the user, see `storeHeader` & `loadHeader` and `storeSection` &
|
||||
## `loadSection`, respectively.
|
||||
##
|
||||
## No Error Handling:
|
||||
## The API is centered around IO and prone to error, each operation checks or
|
||||
## sets the `RodFile.err` field. A user of this API needs to handle these
|
||||
## appropriately.
|
||||
##
|
||||
## API Notes
|
||||
## =========
|
||||
##
|
||||
## Valid inputs for Rod files
|
||||
## --------------------------
|
||||
## ASTs, hopes, dreams, and anything as long as it and any children it may have
|
||||
## support `copyMem`. This means anything that is not a pointer and that does not contain a pointer. At a glance these are:
|
||||
## * string
|
||||
## * objects & tuples (fields are recursed)
|
||||
## * sequences AKA `seq[T]`
|
||||
##
|
||||
## Note on error handling style
|
||||
## ----------------------------
|
||||
## A flag based approach is used where operations no-op in case of a
|
||||
## preexisting error and set the flag if they encounter one.
|
||||
##
|
||||
## Misc
|
||||
## ----
|
||||
## * 'Prim' is short for 'primitive', as in a non-sequence type
|
||||
|
||||
type
|
||||
RodSection* = enum
|
||||
versionSection
|
||||
configSection
|
||||
stringsSection
|
||||
checkSumsSection
|
||||
depsSection
|
||||
numbersSection
|
||||
exportsSection
|
||||
hiddenSection
|
||||
reexportsSection
|
||||
compilerProcsSection
|
||||
trmacrosSection
|
||||
convertersSection
|
||||
methodsSection
|
||||
pureEnumsSection
|
||||
toReplaySection
|
||||
topLevelSection
|
||||
bodiesSection
|
||||
symsSection
|
||||
typesSection
|
||||
typeInstCacheSection
|
||||
procInstCacheSection
|
||||
attachedOpsSection
|
||||
methodsPerGenericTypeSection
|
||||
enumToStringProcsSection
|
||||
methodsPerTypeSection
|
||||
dispatchersSection
|
||||
typeInfoSection # required by the backend
|
||||
backendFlagsSection
|
||||
aliveSymsSection # beware, this is stored in a `.alivesyms` file.
|
||||
sideChannelSection
|
||||
namespaceSection
|
||||
symnamesSection
|
||||
|
||||
RodFileError* = enum
|
||||
ok, tooBig, cannotOpen, ioFailure, wrongHeader, wrongSection, configMismatch,
|
||||
includeFileChanged
|
||||
|
||||
RodFile* = object
|
||||
f*: File
|
||||
currentSection*: RodSection # for error checking
|
||||
err*: RodFileError # little experiment to see if this works
|
||||
# better than exceptions.
|
||||
|
||||
const
|
||||
RodVersion = 2
|
||||
defaultCookie = [byte(0), byte('R'), byte('O'), byte('D'),
|
||||
byte(sizeof(int)*8), byte(system.cpuEndian), byte(0), byte(RodVersion)]
|
||||
|
||||
proc setError(f: var RodFile; err: RodFileError) {.inline.} =
|
||||
f.err = err
|
||||
#raise newException(IOError, "IO error")
|
||||
|
||||
proc storePrim*(f: var RodFile; s: string) =
|
||||
## Stores a string.
|
||||
## The len is prefixed to allow for later retreival.
|
||||
if f.err != ok: return
|
||||
if s.len >= high(int32):
|
||||
setError f, tooBig
|
||||
return
|
||||
var lenPrefix = int32(s.len)
|
||||
if writeBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
setError f, ioFailure
|
||||
else:
|
||||
if s.len != 0:
|
||||
if writeBuffer(f.f, unsafeAddr(s[0]), s.len) != s.len:
|
||||
setError f, ioFailure
|
||||
|
||||
proc storePrim*[T](f: var RodFile; x: T) =
|
||||
## Stores a non-sequence/string `T`.
|
||||
## If `T` doesn't support `copyMem` and is an object or tuple then the fields
|
||||
## are written -- the user from context will need to know which `T` to load.
|
||||
if f.err != ok: return
|
||||
when supportsCopyMem(T):
|
||||
if writeBuffer(f.f, unsafeAddr(x), sizeof(x)) != sizeof(x):
|
||||
setError f, ioFailure
|
||||
elif T is tuple:
|
||||
for y in fields(x):
|
||||
storePrim(f, y)
|
||||
elif T is object:
|
||||
for y in fields(x):
|
||||
when y is seq:
|
||||
storeSeq(f, y)
|
||||
else:
|
||||
storePrim(f, y)
|
||||
else:
|
||||
{.error: "unsupported type for 'storePrim'".}
|
||||
|
||||
proc storeSeq*[T](f: var RodFile; s: seq[T]) =
|
||||
## Stores a sequence of `T`s, with the len as a prefix for later retrieval.
|
||||
if f.err != ok: return
|
||||
if s.len >= high(int32):
|
||||
setError f, tooBig
|
||||
return
|
||||
var lenPrefix = int32(s.len)
|
||||
if writeBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
setError f, ioFailure
|
||||
else:
|
||||
for i in 0..<s.len:
|
||||
storePrim(f, s[i])
|
||||
|
||||
proc storeOrderedTable*[K, T](f: var RodFile; s: OrderedTable[K, T]) =
|
||||
if f.err != ok: return
|
||||
if s.len >= high(int32):
|
||||
setError f, tooBig
|
||||
return
|
||||
var lenPrefix = int32(s.len)
|
||||
if writeBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
setError f, ioFailure
|
||||
else:
|
||||
for _, v in s:
|
||||
storePrim(f, v)
|
||||
|
||||
proc loadPrim*(f: var RodFile; s: var string) =
|
||||
## Read a string, the length was stored as a prefix
|
||||
if f.err != ok: return
|
||||
var lenPrefix = int32(0)
|
||||
if readBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
setError f, ioFailure
|
||||
else:
|
||||
s = newString(lenPrefix)
|
||||
if lenPrefix > 0:
|
||||
if readBuffer(f.f, unsafeAddr(s[0]), s.len) != s.len:
|
||||
setError f, ioFailure
|
||||
|
||||
proc loadPrim*[T](f: var RodFile; x: var T) =
|
||||
## Load a non-sequence/string `T`.
|
||||
if f.err != ok: return
|
||||
when supportsCopyMem(T):
|
||||
if readBuffer(f.f, unsafeAddr(x), sizeof(x)) != sizeof(x):
|
||||
setError f, ioFailure
|
||||
elif T is tuple:
|
||||
for y in fields(x):
|
||||
loadPrim(f, y)
|
||||
elif T is object:
|
||||
for y in fields(x):
|
||||
when y is seq:
|
||||
loadSeq(f, y)
|
||||
else:
|
||||
loadPrim(f, y)
|
||||
else:
|
||||
{.error: "unsupported type for 'loadPrim'".}
|
||||
|
||||
proc loadSeq*[T](f: var RodFile; s: var seq[T]) =
|
||||
## `T` must be compatible with `copyMem`, see `loadPrim`
|
||||
if f.err != ok: return
|
||||
var lenPrefix = int32(0)
|
||||
if readBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
setError f, ioFailure
|
||||
else:
|
||||
s = newSeq[T](lenPrefix)
|
||||
for i in 0..<lenPrefix:
|
||||
loadPrim(f, s[i])
|
||||
|
||||
proc loadOrderedTable*[K, T](f: var RodFile; s: var OrderedTable[K, T]) =
|
||||
## `T` must be compatible with `copyMem`, see `loadPrim`
|
||||
if f.err != ok: return
|
||||
var lenPrefix = int32(0)
|
||||
if readBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
|
||||
setError f, ioFailure
|
||||
else:
|
||||
s = initOrderedTable[K, T](lenPrefix)
|
||||
for i in 0..<lenPrefix:
|
||||
var x = default T
|
||||
loadPrim(f, x)
|
||||
s[x.id] = x
|
||||
|
||||
proc storeHeader*(f: var RodFile; cookie = defaultCookie) =
|
||||
## stores the header which is described by `cookie`.
|
||||
if f.err != ok: return
|
||||
if f.f.writeBytes(cookie, 0, cookie.len) != cookie.len:
|
||||
setError f, ioFailure
|
||||
|
||||
proc loadHeader*(f: var RodFile; cookie = defaultCookie) =
|
||||
## Loads the header which is described by `cookie`.
|
||||
if f.err != ok: return
|
||||
var thisCookie: array[cookie.len, byte] = default(array[cookie.len, byte])
|
||||
if f.f.readBytes(thisCookie, 0, thisCookie.len) != thisCookie.len:
|
||||
setError f, ioFailure
|
||||
elif thisCookie != cookie:
|
||||
setError f, wrongHeader
|
||||
|
||||
proc storeSection*(f: var RodFile; s: RodSection) =
|
||||
## update `currentSection` and writes the bytes value of s.
|
||||
if f.err != ok: return
|
||||
assert f.currentSection < s
|
||||
f.currentSection = s
|
||||
storePrim(f, s)
|
||||
|
||||
proc loadSection*(f: var RodFile; expected: RodSection) =
|
||||
## read the bytes value of s, sets and error if the section is incorrect.
|
||||
if f.err != ok: return
|
||||
var s: RodSection = default(RodSection)
|
||||
loadPrim(f, s)
|
||||
if expected != s and f.err == ok:
|
||||
setError f, wrongSection
|
||||
|
||||
proc create*(filename: string): RodFile =
|
||||
## create the file and open it for writing
|
||||
result = default(RodFile)
|
||||
if not open(result.f, filename, fmWrite):
|
||||
setError result, cannotOpen
|
||||
|
||||
proc close*(f: var RodFile) = close(f.f)
|
||||
|
||||
proc open*(filename: string): RodFile =
|
||||
## open the file for reading
|
||||
result = default(RodFile)
|
||||
if not open(result.f, filename, fmRead):
|
||||
setError result, cannotOpen
|
||||
@@ -108,8 +108,8 @@ proc rawImportSymbol(c: PContext, s, origin: PSym; importSet: var IntSet) =
|
||||
else:
|
||||
importPureEnumField(c, e)
|
||||
else:
|
||||
if s.kind == skConverter: addConverter(c, s)
|
||||
if hasPattern(s): addPattern(c, s)
|
||||
if s.kind == skConverter: addConverter(c, LazySym(sym: s))
|
||||
if hasPattern(s): addPattern(c, LazySym(sym: s))
|
||||
if s.owner != origin:
|
||||
c.exportIndirections.incl((origin.id, s.id))
|
||||
|
||||
@@ -190,19 +190,22 @@ proc addImport(c: PContext; im: sink ImportedModule) =
|
||||
template addUnnamedIt(c: PContext, fromMod: PSym; filter: untyped) {.dirty.} =
|
||||
for it in mitems c.graph.ifaces[fromMod.position].converters:
|
||||
if filter:
|
||||
if sfExported in it.flags:
|
||||
loadPackedSym(c.graph, it)
|
||||
if sfExported in it.sym.flags:
|
||||
addConverter(c, it)
|
||||
for it in mitems c.graph.ifaces[fromMod.position].patterns:
|
||||
if filter:
|
||||
if sfExported in it.flags:
|
||||
loadPackedSym(c.graph, it)
|
||||
if sfExported in it.sym.flags:
|
||||
addPattern(c, it)
|
||||
for it in mitems c.graph.ifaces[fromMod.position].pureEnums:
|
||||
if filter:
|
||||
importPureEnumFields(c, it, it.typ)
|
||||
loadPackedSym(c.graph, it)
|
||||
importPureEnumFields(c, it.sym, it.sym.typ)
|
||||
|
||||
proc importAllSymbolsExcept(c: PContext, fromMod: PSym, exceptSet: IntSet) =
|
||||
c.addImport ImportedModule(m: fromMod, mode: importExcept, exceptSet: exceptSet)
|
||||
addUnnamedIt(c, fromMod, it.name.id notin exceptSet)
|
||||
addUnnamedIt(c, fromMod, it.sym.name.id notin exceptSet)
|
||||
|
||||
proc importAllSymbols*(c: PContext, fromMod: PSym) =
|
||||
c.addImport ImportedModule(m: fromMod, mode: importAll)
|
||||
@@ -289,8 +292,9 @@ proc myImportModule(c: PContext, n: var PNode, importStmtResult: PNode): PSym =
|
||||
c.recursiveDep = err
|
||||
|
||||
let trackUnusedImport = warnUnusedImportX in c.config.notes
|
||||
var realModule: PSym
|
||||
discard pushOptionEntry(c)
|
||||
let realModule = c.graph.importModuleCallback(c.graph, c.module, f)
|
||||
realModule = c.graph.importModuleCallback(c.graph, c.module, f)
|
||||
result = importModuleAs(c, n, realModule, transf.importHidden, trackUnusedImport)
|
||||
popOptionEntry(c)
|
||||
|
||||
|
||||
@@ -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:
|
||||
@@ -495,7 +495,7 @@ proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode =
|
||||
localError(c.graph.config, n.info, errFailedMove,
|
||||
("cannot move '$1', passing '$1' to a sink parameter introduces an implicit copy") % $n)
|
||||
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")
|
||||
@@ -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:
|
||||
|
||||
@@ -316,28 +316,6 @@ proc getNumber(L: var Lexer, result: var Token) =
|
||||
L.bufpos = msgPos
|
||||
lexMessage(L, msgKind, msg % t.literal)
|
||||
|
||||
proc checkBitWidth(L: var Lexer, base: NumericalBase, tokType: TokType,
|
||||
numDigits: int, startpos: int) =
|
||||
# Check bit width for non-base-10 literals
|
||||
# Warn if the digit count exceeds what can fit in the target type
|
||||
let bitsPerDigit = case base
|
||||
of base2: 1
|
||||
of base8: 3
|
||||
of base16: 4
|
||||
else: raiseAssert "unreachable"
|
||||
let bitWidth = case tokType
|
||||
of tkInt8Lit, tkUInt8Lit: 8
|
||||
of tkInt16Lit, tkUInt16Lit: 16
|
||||
of tkInt32Lit, tkUInt32Lit: 32
|
||||
of tkInt64Lit, tkUIntLit, tkIntLit, tkUInt64Lit: 64
|
||||
else: raiseAssert "unreachable"
|
||||
# Maximum digits = ceil(bitWidth / bitsPerDigit) = (bitWidth + bitsPerDigit - 1) div bitsPerDigit
|
||||
let maxDigits = (bitWidth + bitsPerDigit - 1) div bitsPerDigit
|
||||
if numDigits > maxDigits:
|
||||
lexMessageLitNum(L,
|
||||
"number has " & $numDigits & " digits but type only supports " &
|
||||
$maxDigits & " digits: '$1'", startpos, warnLongLiterals)
|
||||
|
||||
var
|
||||
xi: BiggestInt
|
||||
isBase10 = true
|
||||
@@ -513,11 +491,6 @@ proc getNumber(L: var Lexer, result: var Token) =
|
||||
setNumber result.fNumber, (cast[ptr float64](addr(xi)))[]
|
||||
else: internalError(L.config, getLineInfo(L), "getNumber")
|
||||
|
||||
# Check bit width for non-base-10 literals
|
||||
# Warn if the digit count exceeds what can fit in the target type
|
||||
if result.base != base10 and result.tokType in {tkIntLit..tkUInt64Lit} and numDigits > 0:
|
||||
checkBitWidth(L, result.base, result.tokType, numDigits, startpos)
|
||||
|
||||
# Bounds checks. Non decimal literals are allowed to overflow the range of
|
||||
# the datatype as long as their pattern don't overflow _bitwise_, hence
|
||||
# below checks of signed sizes against uint*.high is deliberate:
|
||||
@@ -923,7 +896,7 @@ proc getSymbol(L: var Lexer, tok: var Token) =
|
||||
tok.tokType = tkSymbol
|
||||
else:
|
||||
tok.tokType = TokType(tok.ident.id + ord(tkSymbol))
|
||||
if suspicious and {optStyleHint, optStyleError, optStyleWarning} * L.config.globalOptions != {}:
|
||||
if suspicious and {optStyleHint, optStyleError} * L.config.globalOptions != {}:
|
||||
lintReport(L.config, getLineInfo(L), tok.ident.s.normalize, tok.ident.s)
|
||||
L.bufpos = pos
|
||||
|
||||
|
||||
@@ -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:
|
||||
@@ -484,22 +484,6 @@ proc considerUserDefinedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
|
||||
else:
|
||||
result = false
|
||||
#result = addDestructorCall(c, t, body, x)
|
||||
of attachedDispose:
|
||||
var op = getAttachedOp(c.g, t, c.kind)
|
||||
if op != nil and sfOverridden in op.flags:
|
||||
|
||||
if op.ast.isGenericRoutine:
|
||||
# patch generic destructor:
|
||||
op = instantiateGeneric(c, op, t, t.typeInst)
|
||||
setAttachedOp(c.g, c.idgen.module, t, attachedDispose, op)
|
||||
|
||||
#markUsed(c.g.config, c.info, op, c.g.usageSym)
|
||||
onUse(c.info, op)
|
||||
body.add destructorCall(c, op, x) # fine for `dispose` too!
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
|
||||
of attachedAsgn, attachedSink, attachedTrace:
|
||||
var op = getAttachedOp(c.g, t, c.kind)
|
||||
if op != nil and sfOverridden in op.flags:
|
||||
@@ -574,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)
|
||||
@@ -662,9 +630,6 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
# destroy all elements:
|
||||
forallElements(c, t, body, x, y)
|
||||
body.add genBuiltin(c, mDestroy, "destroy", x)
|
||||
of attachedDispose:
|
||||
# The mDestroy that the C code generator produces is right for `dispose`:
|
||||
body.add genBuiltin(c, mDestroy, "destroy", x)
|
||||
of attachedTrace:
|
||||
if canFormAcycle(c.g, t.elemType):
|
||||
# follow all elements:
|
||||
@@ -701,9 +666,6 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
of attachedDestructor:
|
||||
doAssert t.destructor != nil
|
||||
body.add destructorCall(c, t.destructor, x)
|
||||
of attachedDispose:
|
||||
# The mDestroy that the C code generator produces is right for `dispose`:
|
||||
body.add genBuiltin(c, mDestroy, "destroy", x)
|
||||
of attachedTrace:
|
||||
if t.kind != tyString and canFormAcycle(c.g, t.elemType):
|
||||
let op = getAttachedOp(c.g, t, c.kind)
|
||||
@@ -728,7 +690,7 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
doAssert t.destructor != nil
|
||||
moveCall.add destructorCall(c, t.destructor, x)
|
||||
body.add moveCall
|
||||
of attachedDestructor, attachedDispose:
|
||||
of attachedDestructor:
|
||||
body.add genBuiltin(c, mDestroy, "destroy", x)
|
||||
of attachedTrace:
|
||||
discard "strings are atomic and have no inner elements that are to trace"
|
||||
@@ -759,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
|
||||
@@ -850,8 +783,6 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
of attachedDestructor:
|
||||
body.add genIf(c, cond, actions)
|
||||
of attachedDeepCopy: assert(false, "cannot happen")
|
||||
of attachedDispose:
|
||||
discard "the whole point of this exercise! Do not traverse `ref` fields for `=dispose`!"
|
||||
of attachedTrace:
|
||||
if isCyclic:
|
||||
if isFinal(elemType):
|
||||
@@ -879,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)
|
||||
@@ -931,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:
|
||||
@@ -944,8 +857,6 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRef", c.info, yenv))
|
||||
of attachedDestructor:
|
||||
body.add genIf(c, cond, actions)
|
||||
of attachedDispose:
|
||||
discard "the whole point of this exercise! Do not traverse `closure` fields for `=dispose`!"
|
||||
of attachedDeepCopy: assert(false, "cannot happen")
|
||||
of attachedTrace:
|
||||
body.add callCodegenProc(c.g, "nimTraceRefDyn", c.info, genAddrOf(xenv, c.idgen), y)
|
||||
@@ -976,7 +887,7 @@ proc weakrefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
else:
|
||||
body.sons.insert(des, 0)
|
||||
of attachedDeepCopy: assert(false, "cannot happen")
|
||||
of attachedTrace, attachedDispose: discard
|
||||
of attachedTrace: discard
|
||||
of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x)
|
||||
|
||||
proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
@@ -1004,7 +915,7 @@ proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
of attachedDestructor:
|
||||
body.add genIf(c, x, actions)
|
||||
of attachedDeepCopy: assert(false, "cannot happen")
|
||||
of attachedTrace, attachedDispose: discard
|
||||
of attachedTrace: discard
|
||||
of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x)
|
||||
|
||||
proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
@@ -1017,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
|
||||
@@ -1044,7 +955,7 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
else:
|
||||
body.sons.insert(des, 0)
|
||||
of attachedDeepCopy: assert(false, "cannot happen")
|
||||
of attachedTrace, attachedDispose: discard
|
||||
of attachedTrace: discard
|
||||
of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x)
|
||||
|
||||
proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
@@ -1062,7 +973,7 @@ proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
of attachedDestructor:
|
||||
body.add genIf(c, xx, actions)
|
||||
of attachedDeepCopy: assert(false, "cannot happen")
|
||||
of attachedTrace, attachedDispose: discard
|
||||
of attachedTrace: discard
|
||||
of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x)
|
||||
|
||||
proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
@@ -1072,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):
|
||||
@@ -1081,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)
|
||||
@@ -1142,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:
|
||||
@@ -1194,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)
|
||||
@@ -1221,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:
|
||||
@@ -1237,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)
|
||||
@@ -1266,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:
|
||||
@@ -1305,7 +1213,7 @@ 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:
|
||||
@@ -1427,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
|
||||
|
||||
@@ -93,12 +93,10 @@ type
|
||||
warnBareExcept = "BareExcept",
|
||||
warnImplicitDefaultValue = "ImplicitDefaultValue",
|
||||
warnIgnoredSymbolInjection = "IgnoredSymbolInjection",
|
||||
warnStdPrefix = "StdPrefix",
|
||||
warnUnknownNotes = "UnknownNotes",
|
||||
warnLongLiterals = "LongLiterals",
|
||||
warnStdPrefix = "StdPrefix"
|
||||
warnUnknownNotes = "UnknownNotes"
|
||||
warnUser = "User",
|
||||
warnGlobalVarConstructorTemporary = "GlobalVarConstructorTemporary",
|
||||
warnImplicitRangeConversion = "ImplicitRangeConversion",
|
||||
# hints
|
||||
hintSuccess = "Success", hintSuccessX = "SuccessX",
|
||||
hintCC = "CC",
|
||||
@@ -204,10 +202,8 @@ const
|
||||
warnIgnoredSymbolInjection: "$1",
|
||||
warnStdPrefix: "$1 needs the 'std' prefix",
|
||||
warnUnknownNotes: "$1",
|
||||
warnLongLiterals: "$1",
|
||||
warnUser: "$1",
|
||||
warnGlobalVarConstructorTemporary: "global variable '$1' initialization requires a temporary variable",
|
||||
warnImplicitRangeConversion: "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",
|
||||
@@ -262,7 +258,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, warnImplicitRangeConversion}
|
||||
result[3] = {low(TNoteKind)..high(TNoteKind)} - {warnObservableStores, warnResultUsed, warnAnyEnumConv, warnBareExcept, warnStdPrefix}
|
||||
result[2] = result[3] - {hintStackTrace, hintExtendedContext, hintDeclaredLoc, hintProcessingStmt}
|
||||
result[1] = result[2] - {warnProveField, warnProveIndex,
|
||||
warnGcUnsafe, hintPath, hintDependency, hintCodeBegin, hintCodeEnd,
|
||||
|
||||
@@ -95,7 +95,7 @@ proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) =
|
||||
template styleCheckDef*(ctx: PContext; info: TLineInfo; sym: PSym; k: TSymKind) =
|
||||
## Check symbol definitions adhere to NEP1 style rules.
|
||||
if optStyleCheck in ctx.config.options and # ignore if styleChecks are off
|
||||
{optStyleHint, optStyleError, optStyleWarning} * ctx.config.globalOptions != {} and # check only if hint/error/warning is enabled
|
||||
{optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # check only if hint/error is enabled
|
||||
hintName in ctx.config.notes and # ignore if name checks are not requested
|
||||
ctx.config.belongsToProjectPackageMaybeNil(getModule(ctx.graph, info.fileIndex)) and # ignore foreign packages
|
||||
optStyleUsages notin ctx.config.globalOptions and # ignore if requested to only check name usage
|
||||
@@ -136,7 +136,7 @@ proc styleCheckUseImpl(conf: ConfigRef; info: TLineInfo; s: PSym) =
|
||||
|
||||
template styleCheckUse*(ctx: PContext; info: TLineInfo; sym: PSym) =
|
||||
## Check symbol uses match their definition's style.
|
||||
if {optStyleHint, optStyleError, optStyleWarning} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
|
||||
if {optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
|
||||
hintName in ctx.config.notes and # ignore if name checks are not requested
|
||||
ctx.config.belongsToProjectPackageMaybeNil(getModule(ctx.graph, info.fileIndex)) and # ignore foreign packages
|
||||
sym.kind != skTemp and # ignore temporary variables created by the compiler
|
||||
@@ -152,7 +152,7 @@ proc checkPragmaUseImpl(conf: ConfigRef; info: TLineInfo; w: TSpecialWord; pragm
|
||||
template checkPragmaUse*(ctx: PContext; info: TLineInfo; w: TSpecialWord; pragmaName: string, sym: PSym) =
|
||||
## Check builtin pragma uses match their definition's style.
|
||||
## Note: This only applies to builtin pragmas, not user pragmas.
|
||||
if {optStyleHint, optStyleError, optStyleWarning} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
|
||||
if {optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
|
||||
hintName in ctx.config.notes and # ignore if name checks are not requested
|
||||
ctx.config.belongsToProjectPackageMaybeNil(getModule(ctx.graph, info.fileIndex)): # ignore foreign packages
|
||||
checkPragmaUseImpl(ctx.config, info, w, pragmaName)
|
||||
|
||||
@@ -412,6 +412,8 @@ proc addDecl*(c: PContext, sym: PSym) {.inline.} =
|
||||
proc addPrelimDecl*(c: PContext, sym: PSym) =
|
||||
discard c.currentScope.addUniqueSym(sym)
|
||||
|
||||
from ic / ic import addHidden
|
||||
|
||||
proc addInterfaceDeclAux(c: PContext, sym: PSym) =
|
||||
## adds symbol to the module for either private or public access.
|
||||
if sfExported in sym.flags:
|
||||
@@ -420,6 +422,8 @@ proc addInterfaceDeclAux(c: PContext, sym: PSym) =
|
||||
else: internalError(c.config, sym.info, "addInterfaceDeclAux")
|
||||
elif sym.kind in ExportableSymKinds and c.module != nil and isTopLevelInsideDeclaration(c, sym):
|
||||
strTableAdd(semtabAll(c.graph, c.module), sym)
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
addHidden(c.encoder, c.packedRepr, sym)
|
||||
|
||||
proc addInterfaceDeclAt*(c: PContext, scope: PScope, sym: PSym) =
|
||||
## adds a symbol on the scope and the interface if appropriate
|
||||
|
||||
@@ -26,6 +26,8 @@ import
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/[syncio, assertions]
|
||||
|
||||
import ic / [cbackend, integrity, navigator, ic]
|
||||
|
||||
import ../dist/checksums/src/checksums/sha1
|
||||
|
||||
import pipelines
|
||||
@@ -97,6 +99,14 @@ proc commandCheck(graph: ModuleGraph) =
|
||||
setPipeLinePass(graph, SemPass)
|
||||
compilePipelineProject(graph)
|
||||
|
||||
if conf.symbolFiles != disabledSf:
|
||||
case conf.ideCmd
|
||||
of ideDef: navDefinition(graph)
|
||||
of ideUse: navUsages(graph)
|
||||
of ideDus: navDefusages(graph)
|
||||
else: discard
|
||||
writeRodFiles(graph)
|
||||
|
||||
when not defined(leanCompiler):
|
||||
proc commandDoc2(graph: ModuleGraph; ext: string) =
|
||||
handleDocOutputOptions graph.config
|
||||
@@ -163,7 +173,15 @@ proc commandCompileToC(graph: ModuleGraph) =
|
||||
compilePipelineProject(graph)
|
||||
if graph.config.errorCounter > 0:
|
||||
return # issue #9933
|
||||
cgenWriteModules(graph.backend, conf)
|
||||
if conf.symbolFiles == disabledSf:
|
||||
cgenWriteModules(graph.backend, conf)
|
||||
else:
|
||||
if isDefined(conf, "nimIcIntegrityChecks"):
|
||||
checkIntegrity(graph)
|
||||
generateCode(graph)
|
||||
# graph.backend can be nil under IC when nothing changed at all:
|
||||
if graph.backend != nil:
|
||||
cgenWriteModules(graph.backend, conf)
|
||||
if conf.cmd != cmdTcc and graph.backend != nil:
|
||||
extccomp.callCCompiler(conf)
|
||||
# for now we do not support writing out a .json file with the build instructions when HCR is on
|
||||
@@ -223,6 +241,10 @@ proc commandScan(cache: IdentCache, config: ConfigRef) =
|
||||
else:
|
||||
rawMessage(config, errGenerated, "cannot open file: " & f.string)
|
||||
|
||||
proc commandView(graph: ModuleGraph) =
|
||||
let f = toAbsolute(mainCommandArg(graph.config), AbsoluteDir getCurrentDir()).addFileExt(RodExt)
|
||||
rodViewer(f, graph.config, graph.cache)
|
||||
|
||||
const
|
||||
PrintRopeCacheStats = false
|
||||
|
||||
@@ -320,6 +342,8 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
case conf.cmd
|
||||
of cmdBackends:
|
||||
compileToBackend()
|
||||
when BenchIC:
|
||||
echoTimes graph.packed
|
||||
of cmdTcc:
|
||||
when hasTinyCBackend:
|
||||
extccomp.setCC(conf, "tcc", unknownLineInfo)
|
||||
@@ -437,6 +461,10 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
of cmdParse:
|
||||
wantMainModule(conf)
|
||||
discard parseFile(conf.projectMainIdx, cache, conf)
|
||||
of cmdRod:
|
||||
wantMainModule(conf)
|
||||
commandView(graph)
|
||||
#msgWriteln(conf, "Beware: Indentation tokens depend on the parser's state!")
|
||||
of cmdInteractive: commandInteractive(graph)
|
||||
of cmdNimscript:
|
||||
if conf.projectIsCmd or conf.projectIsStdin: discard
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import std/[intsets, tables, hashes, strtabs, os, strutils, parseutils]
|
||||
import ../dist/checksums/src/checksums/md5
|
||||
import ast, astalgo, options, lineinfos,idents, btrees, ropes, msgs, pathutils, packages, suggestsymdb
|
||||
import ic / [packed_ast, ic]
|
||||
|
||||
when not defined(nimKochBootstrap):
|
||||
import ast2nif
|
||||
@@ -27,12 +28,16 @@ when defined(nimPreviewSlimSystem):
|
||||
type
|
||||
SigHash* = distinct MD5Digest
|
||||
|
||||
LazySym* = object
|
||||
id*: FullId
|
||||
sym*: PSym
|
||||
|
||||
Iface* = object ## data we don't want to store directly in the
|
||||
## ast.PSym type for s.kind == skModule
|
||||
module*: PSym ## module this "Iface" belongs to
|
||||
converters*: seq[PSym]
|
||||
patterns*: seq[PSym]
|
||||
pureEnums*: seq[PSym]
|
||||
converters*: seq[LazySym]
|
||||
patterns*: seq[LazySym]
|
||||
pureEnums*: seq[LazySym]
|
||||
interf: TStrTable
|
||||
interfHidden: TStrTable
|
||||
uniqueName*: Rope
|
||||
@@ -41,6 +46,20 @@ type
|
||||
opNot*, opContains*, opLe*, opLt*, opAnd*, opOr*, opIsNil*, opEq*: PSym
|
||||
opAdd*, opSub*, opMul*, opDiv*, opLen*: PSym
|
||||
|
||||
FullId* = object
|
||||
module*: int
|
||||
packed*: PackedItemId
|
||||
|
||||
LazyType* = object
|
||||
id*: FullId
|
||||
typ*: PType
|
||||
|
||||
LazyInstantiation* = object
|
||||
module*: int
|
||||
sym*: FullId
|
||||
concreteTypes*: seq[FullId]
|
||||
inst*: PInstantiation
|
||||
|
||||
PipelinePass* = enum
|
||||
NonePass
|
||||
SemPass
|
||||
@@ -56,18 +75,21 @@ type
|
||||
|
||||
ModuleGraph* {.acyclic.} = ref object
|
||||
ifaces*: seq[Iface] ## indexed by int32 fileIdx
|
||||
packed*: PackedModuleGraph
|
||||
encoders*: seq[PackedEncoder]
|
||||
|
||||
typeInstCache*: Table[ItemId, seq[PType]] # A symbol's ItemId.
|
||||
procInstCache*: Table[ItemId, seq[PInstantiation]] # A symbol's ItemId.
|
||||
attachedOps*: array[TTypeAttachedOp, Table[ItemId, PSym]] # Type ID, destructors, etc.
|
||||
typeInstCache*: Table[ItemId, seq[LazyType]] # A symbol's ItemId.
|
||||
procInstCache*: Table[ItemId, seq[LazyInstantiation]] # A symbol's ItemId.
|
||||
attachedOps*: array[TTypeAttachedOp, Table[ItemId, LazySym]] # Type ID, destructors, etc.
|
||||
loadedOps: array[TTypeAttachedOp, Table[string, PSym]] # This can later by unified with `attachedOps` once it's stable
|
||||
opsLog*: seq[LogEntry]
|
||||
methodsPerGenericType*: Table[ItemId, seq[(int, PSym)]] # Type ID, attached methods
|
||||
methodsPerGenericType*: Table[ItemId, seq[(int, LazySym)]] # Type ID, attached methods
|
||||
memberProcsPerType*: Table[ItemId, seq[PSym]] # Type ID, attached member procs (only c++, virtual,member and ctor so far).
|
||||
initializersPerType*: Table[ItemId, PNode] # Type ID, AST call to the default ctor (c++ only)
|
||||
enumToStringProcs*: Table[ItemId, PSym]
|
||||
enumToStringProcs*: Table[ItemId, LazySym]
|
||||
emittedTypeInfo*: Table[string, FileIndex]
|
||||
|
||||
startupPackedConfig*: PackedConfig
|
||||
packageSyms*: TStrTable
|
||||
deps*: IntSet # the dependency graph or potentially its transitive closure.
|
||||
importDeps*: Table[FileIndex, seq[FileIndex]] # explicit import module dependencies
|
||||
@@ -93,8 +115,8 @@ type
|
||||
methods*: seq[tuple[methods: seq[PSym], dispatcher: PSym]] # needs serialization!
|
||||
bucketTable*: CountTable[ItemId]
|
||||
objectTree*: Table[ItemId, seq[tuple[depth: int, value: PType]]]
|
||||
methodsPerType*: Table[ItemId, seq[PSym]]
|
||||
dispatchers*: seq[PSym]
|
||||
methodsPerType*: Table[ItemId, seq[LazySym]]
|
||||
dispatchers*: seq[LazySym]
|
||||
|
||||
systemModule*: PSym
|
||||
sysTypes*: array[TTypeKind, PType]
|
||||
@@ -124,7 +146,6 @@ type
|
||||
|
||||
procGlobals*: seq[PNode]
|
||||
nifReplayActions*: Table[int32, seq[PNode]] # module position -> replay actions for NIF
|
||||
cachedMods: IntSet
|
||||
|
||||
TPassContext* = object of RootObj # the pass's context
|
||||
idgen*: IdGenerator
|
||||
@@ -207,43 +228,85 @@ proc strTableAdds*(g: ModuleGraph, m: PSym, s: PSym) =
|
||||
strTableAdd(semtabAll(g, m), s)
|
||||
|
||||
proc isCachedModule(g: ModuleGraph; module: int): bool {.inline.} =
|
||||
result = module in g.cachedMods
|
||||
result = module < g.packed.len and g.packed[module].status == loaded
|
||||
|
||||
proc isCachedModule*(g: ModuleGraph; m: PSym): bool {.inline.} =
|
||||
isCachedModule(g, m.position)
|
||||
|
||||
proc simulateCachedModule(g: ModuleGraph; moduleSym: PSym; m: PackedModule) =
|
||||
when false:
|
||||
echo "simulating ", moduleSym.name.s, " ", moduleSym.position
|
||||
simulateLoadedModule(g.packed, g.config, g.cache, moduleSym, m)
|
||||
|
||||
proc initEncoder*(g: ModuleGraph; module: PSym) =
|
||||
let id = module.position
|
||||
if id >= g.encoders.len:
|
||||
setLen g.encoders, id+1
|
||||
ic.initEncoder(g.encoders[id],
|
||||
g.packed[id].fromDisk, module, g.config, g.startupPackedConfig)
|
||||
|
||||
type
|
||||
ModuleIter* = object
|
||||
fromRod: bool
|
||||
modIndex: int
|
||||
ti: TIdentIter
|
||||
rodIt: RodIter
|
||||
importHidden: bool
|
||||
|
||||
proc initModuleIter*(mi: var ModuleIter; g: ModuleGraph; m: PSym; name: PIdent): PSym =
|
||||
assert m.kind == skModule
|
||||
mi.modIndex = m.position
|
||||
mi.fromRod = isCachedModule(g, mi.modIndex)
|
||||
mi.importHidden = optImportHidden in m.options
|
||||
result = initIdentIter(mi.ti, g.ifaces[mi.modIndex].interfSelect(mi.importHidden), name)
|
||||
if mi.fromRod:
|
||||
result = initRodIter(mi.rodIt, g.config, g.cache, g.packed, FileIndex mi.modIndex, name, mi.importHidden)
|
||||
else:
|
||||
result = initIdentIter(mi.ti, g.ifaces[mi.modIndex].interfSelect(mi.importHidden), name)
|
||||
|
||||
proc nextModuleIter*(mi: var ModuleIter; g: ModuleGraph): PSym =
|
||||
result = nextIdentIter(mi.ti, g.ifaces[mi.modIndex].interfSelect(mi.importHidden))
|
||||
if mi.fromRod:
|
||||
result = nextRodIter(mi.rodIt, g.packed)
|
||||
else:
|
||||
result = nextIdentIter(mi.ti, g.ifaces[mi.modIndex].interfSelect(mi.importHidden))
|
||||
|
||||
iterator allSyms*(g: ModuleGraph; m: PSym): PSym =
|
||||
let importHidden = optImportHidden in m.options
|
||||
for s in g.ifaces[m.position].interfSelect(importHidden).data:
|
||||
if s != nil:
|
||||
yield s
|
||||
if isCachedModule(g, m):
|
||||
var rodIt: RodIter = default(RodIter)
|
||||
var r = initRodIterAllSyms(rodIt, g.config, g.cache, g.packed, FileIndex m.position, importHidden)
|
||||
while r != nil:
|
||||
yield r
|
||||
r = nextRodIter(rodIt, g.packed)
|
||||
else:
|
||||
for s in g.ifaces[m.position].interfSelect(importHidden).data:
|
||||
if s != nil:
|
||||
yield s
|
||||
|
||||
proc someSym*(g: ModuleGraph; m: PSym; name: PIdent): PSym =
|
||||
let importHidden = optImportHidden in m.options
|
||||
result = strTableGet(g.ifaces[m.position].interfSelect(importHidden), name)
|
||||
if isCachedModule(g, m):
|
||||
result = interfaceSymbol(g.config, g.cache, g.packed, FileIndex(m.position), name, importHidden)
|
||||
else:
|
||||
result = strTableGet(g.ifaces[m.position].interfSelect(importHidden), name)
|
||||
|
||||
proc someSymAmb*(g: ModuleGraph; m: PSym; name: PIdent; amb: var bool): PSym =
|
||||
let importHidden = optImportHidden in m.options
|
||||
var ti: TIdentIter = default(TIdentIter)
|
||||
result = initIdentIter(ti, g.ifaces[m.position].interfSelect(importHidden), name)
|
||||
if result != nil and nextIdentIter(ti, g.ifaces[m.position].interfSelect(importHidden)) != nil:
|
||||
# another symbol exists with same name
|
||||
amb = true
|
||||
if isCachedModule(g, m):
|
||||
result = nil
|
||||
for s in interfaceSymbols(g.config, g.cache, g.packed, FileIndex(m.position), name, importHidden):
|
||||
if result == nil:
|
||||
# set result to the first symbol
|
||||
result = s
|
||||
else:
|
||||
# another symbol found
|
||||
amb = true
|
||||
break
|
||||
else:
|
||||
var ti: TIdentIter = default(TIdentIter)
|
||||
result = initIdentIter(ti, g.ifaces[m.position].interfSelect(importHidden), name)
|
||||
if result != nil and nextIdentIter(ti, g.ifaces[m.position].interfSelect(importHidden)) != nil:
|
||||
# another symbol exists with same name
|
||||
amb = true
|
||||
|
||||
proc systemModuleSym*(g: ModuleGraph; name: PIdent): PSym =
|
||||
result = someSym(g, g.systemModule, name)
|
||||
@@ -255,24 +318,56 @@ iterator systemModuleSyms*(g: ModuleGraph; name: PIdent): PSym =
|
||||
yield r
|
||||
r = nextModuleIter(mi, g)
|
||||
|
||||
proc resolveType(g: ModuleGraph; t: var LazyType): PType =
|
||||
result = t.typ
|
||||
if result == nil and isCachedModule(g, t.id.module):
|
||||
result = loadTypeFromId(g.config, g.cache, g.packed, t.id.module, t.id.packed)
|
||||
t.typ = result
|
||||
assert result != nil
|
||||
|
||||
proc resolveSym(g: ModuleGraph; t: var LazySym): PSym =
|
||||
result = t.sym
|
||||
if result == nil and isCachedModule(g, t.id.module):
|
||||
result = loadSymFromId(g.config, g.cache, g.packed, t.id.module, t.id.packed)
|
||||
t.sym = result
|
||||
assert result != nil
|
||||
|
||||
proc resolveInst(g: ModuleGraph; t: var LazyInstantiation): PInstantiation =
|
||||
result = t.inst
|
||||
if result == nil and isCachedModule(g, t.module):
|
||||
result = PInstantiation(sym: loadSymFromId(g.config, g.cache, g.packed, t.sym.module, t.sym.packed))
|
||||
result.concreteTypes = newSeq[PType](t.concreteTypes.len)
|
||||
for i in 0..high(result.concreteTypes):
|
||||
result.concreteTypes[i] = loadTypeFromId(g.config, g.cache, g.packed,
|
||||
t.concreteTypes[i].module, t.concreteTypes[i].packed)
|
||||
t.inst = result
|
||||
assert result != nil
|
||||
|
||||
proc resolveAttachedOp*(g: ModuleGraph; t: var LazySym): PSym =
|
||||
result = t.sym
|
||||
if result == nil:
|
||||
result = loadSymFromId(g.config, g.cache, g.packed, t.id.module, t.id.packed)
|
||||
t.sym = result
|
||||
assert result != nil
|
||||
|
||||
iterator typeInstCacheItems*(g: ModuleGraph; s: PSym): PType =
|
||||
if g.typeInstCache.contains(s.itemId):
|
||||
let x = addr(g.typeInstCache[s.itemId])
|
||||
for t in mitems(x[]):
|
||||
yield t
|
||||
yield resolveType(g, t)
|
||||
|
||||
iterator procInstCacheItems*(g: ModuleGraph; s: PSym): PInstantiation =
|
||||
if g.procInstCache.contains(s.itemId):
|
||||
let x = addr(g.procInstCache[s.itemId])
|
||||
for t in mitems(x[]):
|
||||
yield t
|
||||
yield resolveInst(g, t)
|
||||
|
||||
|
||||
proc getAttachedOp*(g: ModuleGraph; t: PType; op: TTypeAttachedOp): PSym =
|
||||
## returns the requested attached operation for type `t`. Can return nil
|
||||
## if no such operation exists.
|
||||
if g.attachedOps[op].contains(t.itemId):
|
||||
result = g.attachedOps[op][t.itemId]
|
||||
result = resolveAttachedOp(g, g.attachedOps[op][t.itemId])
|
||||
elif g.config.cmd in {cmdNifC, cmdM}:
|
||||
# Fall back to key-based lookup for NIF-loaded hooks
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
@@ -293,32 +388,36 @@ proc setAttachedOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp;
|
||||
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: module
|
||||
g.opsLog.add LogEntry(kind: HookEntry, op: op, module: ownerModule, key: key, sym: value)
|
||||
g.loadedOps[op][key] = value
|
||||
g.attachedOps[op][t.itemId] = value
|
||||
g.attachedOps[op][t.itemId] = LazySym(sym: value)
|
||||
|
||||
proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttachedOp; value: PSym) =
|
||||
## Overload that takes ItemId directly, useful for registering hooks from NIF index.
|
||||
g.attachedOps[op][typeId] = value
|
||||
g.attachedOps[op][typeId] = LazySym(sym: value)
|
||||
|
||||
proc setAttachedOpPartial*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) =
|
||||
## we also need to record this to the packed module.
|
||||
g.attachedOps[op][t.itemId] = value
|
||||
g.attachedOps[op][t.itemId] = LazySym(sym: value)
|
||||
|
||||
proc completePartialOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) {.inline.} =
|
||||
discard
|
||||
proc completePartialOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) =
|
||||
if g.config.symbolFiles != disabledSf:
|
||||
assert module < g.encoders.len
|
||||
assert isActive(g.encoders[module])
|
||||
toPackedGeneratedProcDef(value, g.encoders[module], g.packed[module].fromDisk)
|
||||
#storeAttachedProcDef(t, op, value, g.encoders[module], g.packed[module].fromDisk)
|
||||
|
||||
iterator getDispatchers*(g: ModuleGraph): PSym =
|
||||
for i in g.dispatchers.mitems:
|
||||
yield i
|
||||
yield resolveSym(g, i)
|
||||
|
||||
proc addDispatchers*(g: ModuleGraph, value: PSym) =
|
||||
# TODO: add it for packed modules
|
||||
g.dispatchers.add value
|
||||
g.dispatchers.add LazySym(sym: value)
|
||||
|
||||
iterator resolveLazySymSeq(g: ModuleGraph, list: var seq[PSym]): PSym =
|
||||
iterator resolveLazySymSeq(g: ModuleGraph, list: var seq[LazySym]): PSym =
|
||||
for it in list.mitems:
|
||||
yield it
|
||||
yield resolveSym(g, it)
|
||||
|
||||
proc setMethodsPerType*(g: ModuleGraph; id: ItemId, methods: seq[PSym]) =
|
||||
proc setMethodsPerType*(g: ModuleGraph; id: ItemId, methods: seq[LazySym]) =
|
||||
# TODO: add it for packed modules
|
||||
g.methodsPerType[id] = methods
|
||||
|
||||
@@ -329,14 +428,14 @@ proc addNifReplayAction*(g: ModuleGraph; module: int32; n: PNode) =
|
||||
iterator getMethodsPerType*(g: ModuleGraph; t: PType): PSym =
|
||||
if g.methodsPerType.contains(t.itemId):
|
||||
for it in mitems g.methodsPerType[t.itemId]:
|
||||
yield it
|
||||
yield resolveSym(g, it)
|
||||
|
||||
proc getToStringProc*(g: ModuleGraph; t: PType): PSym =
|
||||
result = g.enumToStringProcs[t.itemId]
|
||||
result = resolveSym(g, g.enumToStringProcs[t.itemId])
|
||||
assert result != nil
|
||||
|
||||
proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) =
|
||||
g.enumToStringProcs[t.itemId] = value
|
||||
g.enumToStringProcs[t.itemId] = LazySym(sym: value)
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: value.itemId.module.int
|
||||
g.opsLog.add LogEntry(kind: EnumToStrEntry, module: ownerModule, key: key, sym: value)
|
||||
@@ -344,10 +443,10 @@ proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) =
|
||||
iterator methodsForGeneric*(g: ModuleGraph; t: PType): (int, PSym) =
|
||||
if g.methodsPerGenericType.contains(t.itemId):
|
||||
for it in mitems g.methodsPerGenericType[t.itemId]:
|
||||
yield (it[0], it[1])
|
||||
yield (it[0], resolveSym(g, it[1]))
|
||||
|
||||
proc addMethodToGeneric*(g: ModuleGraph; module: int; t: PType; col: int; m: PSym) =
|
||||
g.methodsPerGenericType.mgetOrPut(t.itemId, @[]).add (col, m)
|
||||
g.methodsPerGenericType.mgetOrPut(t.itemId, @[]).add (col, LazySym(sym: m))
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: module
|
||||
g.opsLog.add LogEntry(kind: MethodEntry, module: ownerModule, key: key, sym: m)
|
||||
@@ -375,12 +474,11 @@ proc loadCompilerProc*(g: ModuleGraph; name: string): PSym =
|
||||
if g.config.symbolFiles == disabledSf and optWithinConfigSystem notin g.config.globalOptions:
|
||||
# For NIF-based compilation, search in loaded NIF modules
|
||||
when not defined(nimKochBootstrap):
|
||||
# Try to resolve from NIF for both cmdNifC and cmdM (which uses NIF files)
|
||||
if g.config.cmd in {cmdNifC, cmdM}:
|
||||
# Only try to resolve from NIF if we're actually using NIF files (cmdNifC)
|
||||
if g.config.cmd == cmdNifC:
|
||||
# First try system module (most compilerprocs are there)
|
||||
let systemFileIdx = g.config.m.systemFileIdx
|
||||
if systemFileIdx != InvalidFileIdx and not g.withinSystem:
|
||||
# Only try to load from NIF if the file exists (it may not during initial ic build)
|
||||
if systemFileIdx != InvalidFileIdx:
|
||||
result = tryResolveCompilerProc(ast.program, name, systemFileIdx)
|
||||
if result != nil:
|
||||
strTableAdd(g.compilerprocs, result)
|
||||
@@ -398,6 +496,20 @@ proc loadCompilerProc*(g: ModuleGraph; name: string): PSym =
|
||||
return result
|
||||
return nil
|
||||
|
||||
# slow, linear search, but the results are cached:
|
||||
for module in 0..<len(g.packed):
|
||||
#if isCachedModule(g, module):
|
||||
let x = searchForCompilerproc(g.packed[module], name)
|
||||
if x >= 0:
|
||||
result = loadSymFromId(g.config, g.cache, g.packed, module, toPackedItemId(x))
|
||||
if result != nil:
|
||||
strTableAdd(g.compilerprocs, result)
|
||||
return result
|
||||
|
||||
proc loadPackedSym*(g: ModuleGraph; s: var LazySym) =
|
||||
if s.sym == nil:
|
||||
s.sym = loadSymFromId(g.config, g.cache, g.packed, s.id.module, s.id.packed)
|
||||
|
||||
proc `$`*(u: SigHash): string =
|
||||
toBase64a(cast[cstring](unsafeAddr u), sizeof(u))
|
||||
|
||||
@@ -484,13 +596,16 @@ proc registerModule*(g: ModuleGraph; m: PSym) =
|
||||
if m.position >= g.ifaces.len:
|
||||
setLen(g.ifaces, m.position + 1)
|
||||
|
||||
if m.position >= g.packed.len:
|
||||
setLen(g.packed.pm, m.position + 1)
|
||||
|
||||
if g.ifaces[m.position].module == nil:
|
||||
g.ifaces[m.position] = Iface(module: m, converters: @[], patterns: @[],
|
||||
uniqueName: rope(uniqueModuleName(g.config, m)))
|
||||
initStrTables(g, m)
|
||||
|
||||
proc registerModuleById*(g: ModuleGraph; m: FileIndex) =
|
||||
registerModule(g, g.ifaces[int m].module)
|
||||
registerModule(g, g.packed[int m].module)
|
||||
|
||||
proc initOperators*(g: ModuleGraph): Operators =
|
||||
# These are safe for IC.
|
||||
@@ -537,7 +652,6 @@ proc initModuleGraphFields(result: ModuleGraph) =
|
||||
result.operators = initOperators(result)
|
||||
result.emittedTypeInfo = initTable[string, FileIndex]()
|
||||
result.cachedFiles = newStringTable()
|
||||
result.cachedMods = initIntSet()
|
||||
|
||||
proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
|
||||
result = ModuleGraph()
|
||||
@@ -560,13 +674,49 @@ proc resetAllModules*(g: ModuleGraph) =
|
||||
initModuleGraphFields(g)
|
||||
|
||||
proc getModule*(g: ModuleGraph; fileIdx: FileIndex): PSym =
|
||||
if fileIdx.int32 >= 0 and fileIdx.int32 < g.ifaces.len:
|
||||
result = g.ifaces[fileIdx.int32].module
|
||||
else:
|
||||
result = nil
|
||||
result = nil
|
||||
if fileIdx.int32 >= 0:
|
||||
if isCachedModule(g, fileIdx.int32):
|
||||
result = g.packed[fileIdx.int32].module
|
||||
elif fileIdx.int32 < g.ifaces.len:
|
||||
result = g.ifaces[fileIdx.int32].module
|
||||
|
||||
proc moduleOpenForCodegen*(g: ModuleGraph; m: FileIndex): bool {.inline.} =
|
||||
result = true
|
||||
if g.config.symbolFiles == disabledSf:
|
||||
result = true
|
||||
else:
|
||||
result = g.packed[m.int32].status notin {undefined, stored, loaded}
|
||||
|
||||
proc rememberEmittedTypeInfo*(g: ModuleGraph; m: FileIndex; ti: string) =
|
||||
#assert(not isCachedModule(g, m.int32))
|
||||
if g.config.symbolFiles != disabledSf:
|
||||
#assert g.encoders[m.int32].isActive
|
||||
assert g.packed[m.int32].status != stored
|
||||
g.packed[m.int32].fromDisk.emittedTypeInfo.add ti
|
||||
#echo "added typeinfo ", m.int32, " ", ti, " suspicious ", not g.encoders[m.int32].isActive
|
||||
|
||||
proc rememberFlag*(g: ModuleGraph; m: PSym; flag: ModuleBackendFlag) =
|
||||
if g.config.symbolFiles != disabledSf:
|
||||
#assert g.encoders[m.int32].isActive
|
||||
assert g.packed[m.position].status != stored
|
||||
g.packed[m.position].fromDisk.backendFlags.incl flag
|
||||
|
||||
proc closeRodFile*(g: ModuleGraph; m: PSym) =
|
||||
if g.config.symbolFiles in {readOnlySf, v2Sf}:
|
||||
# For stress testing we seek to reload the symbols from memory. This
|
||||
# way much of the logic is tested but the test is reproducible as it does
|
||||
# not depend on the hard disk contents!
|
||||
let mint = m.position
|
||||
saveRodFile(toRodFile(g.config, AbsoluteFile toFullPath(g.config, FileIndex(mint))),
|
||||
g.encoders[mint], g.packed[mint].fromDisk)
|
||||
g.packed[mint].status = stored
|
||||
|
||||
elif g.config.symbolFiles == stressTest:
|
||||
# debug code, but maybe a good idea for production? Could reduce the compiler's
|
||||
# memory consumption considerably at the cost of more loads from disk.
|
||||
let mint = m.position
|
||||
simulateCachedModule(g, m, g.packed[mint].fromDisk)
|
||||
g.packed[mint].status = loaded
|
||||
|
||||
proc dependsOn(a, b: int): int {.inline.} = (a shl 15) + b
|
||||
|
||||
@@ -650,8 +800,19 @@ proc needsCompilation*(g: ModuleGraph, fileIdx: FileIndex): bool =
|
||||
|
||||
proc getBody*(g: ModuleGraph; s: PSym): PNode {.inline.} =
|
||||
result = s.ast[bodyPos]
|
||||
if result == nil and g.config.symbolFiles in {readOnlySf, v2Sf, stressTest}:
|
||||
result = loadProcBody(g.config, g.cache, g.packed, s)
|
||||
s.ast[bodyPos] = result
|
||||
assert result != nil
|
||||
|
||||
proc moduleFromRodFile*(g: ModuleGraph; fileIdx: FileIndex;
|
||||
cachedModules: var seq[FileIndex]): PSym =
|
||||
## Returns 'nil' if the module needs to be recompiled.
|
||||
if g.config.symbolFiles in {readOnlySf, v2Sf, stressTest}:
|
||||
result = moduleFromRodFile(g.packed, g.config, g.cache, fileIdx, cachedModules)
|
||||
else:
|
||||
result = nil
|
||||
|
||||
when not defined(nimKochBootstrap):
|
||||
proc moduleFromNifFile*(g: ModuleGraph; fileIdx: FileIndex;
|
||||
flags: set[LoadFlag] = {}): PrecompiledModule =
|
||||
@@ -679,16 +840,13 @@ when not defined(nimKochBootstrap):
|
||||
g.ifaces[fileIdx.int].interfHidden, flags)
|
||||
result.module = m
|
||||
|
||||
# Mark module as cached
|
||||
g.cachedMods.incl fileIdx.int
|
||||
|
||||
# Register hooks from NIF index with the module graph
|
||||
for x in result.logOps:
|
||||
case x.kind
|
||||
of HookEntry:
|
||||
g.loadedOps[x.op][x.key] = x.sym
|
||||
of ConverterEntry:
|
||||
g.ifaces[fileIdx.int].converters.add x.sym
|
||||
g.ifaces[fileIdx.int].converters.add LazySym(sym: x.sym)
|
||||
of MethodEntry:
|
||||
discard "todo"
|
||||
of EnumToStrEntry:
|
||||
@@ -699,10 +857,9 @@ when not defined(nimKochBootstrap):
|
||||
discard "todo"
|
||||
|
||||
proc configComplete*(g: ModuleGraph) =
|
||||
#rememberStartupConfig(g.startupPackedConfig, g.config)
|
||||
discard
|
||||
rememberStartupConfig(g.startupPackedConfig, g.config)
|
||||
|
||||
proc onProcessing*(graph: ModuleGraph, fileIdx: FileIndex, moduleStatus: string, fromModule: PSym) =
|
||||
proc onProcessing*(graph: ModuleGraph, fileIdx: FileIndex, moduleStatus: string, fromModule: PSym, ) =
|
||||
let conf = graph.config
|
||||
let isNimscript = conf.isDefined("nimscript")
|
||||
if (not isNimscript) or hintProcessing in conf.cmdlineNotes:
|
||||
|
||||
@@ -109,11 +109,9 @@ proc mangleModuleName*(conf: ConfigRef; path: AbsoluteFile): string =
|
||||
of FromSearchPath: "@p"
|
||||
of FromNimblePath: "@n"
|
||||
|
||||
# Note: We encode ".." specially as "@d" to avoid issues with changeFileExt
|
||||
# which would misinterpret ".." as "name.ext" and strip the second part.
|
||||
prefix & best.multiReplace(
|
||||
{"..": "@d", $os.DirSep: "@s", $os.AltSep: "@s", "#": "@h", "@": "@@", ":": "@c"})
|
||||
{$os.DirSep: "@s", $os.AltSep: "@s", "#": "@h", "@": "@@", ":": "@c"})
|
||||
|
||||
proc demangleModuleName*(path: string): string =
|
||||
## Demangle a relative module path.
|
||||
result = path.multiReplace({"@@": "@", "@d": "..", "@h": "#", "@s": "/", "@m": "", "@p": "", "@n": "", "@c": ":"})
|
||||
result = path.multiReplace({"@@": "@", "@h": "#", "@s": "/", "@m": "", "@p": "", "@n": "", "@c": ":"})
|
||||
|
||||
@@ -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)
|
||||
@@ -664,9 +664,7 @@ template internalAssert*(conf: ConfigRef, e: bool) =
|
||||
|
||||
template lintReport*(conf: ConfigRef; info: TLineInfo, beau, got: string, extraMsg = "") =
|
||||
let m = "'$1' should be: '$2'$3" % [got, beau, extraMsg]
|
||||
let msg = if optStyleError in conf.globalOptions: errGenerated
|
||||
elif optStyleWarning in conf.globalOptions: warnUser
|
||||
else: hintName
|
||||
let msg = if optStyleError in conf.globalOptions: errGenerated else: hintName
|
||||
liMessage(conf, info, msg, m, doNothing, instLoc())
|
||||
|
||||
proc quotedFilename*(conf: ConfigRef; fi: FileIndex): Rope =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -68,7 +68,6 @@ type # please make sure we have under 32 options
|
||||
optUseNimcache, # save artifacts (including binary) in $nimcache
|
||||
optStyleHint, # check that the names adhere to NEP-1
|
||||
optStyleError, # enforce that the names adhere to NEP-1
|
||||
optStyleWarning, # emit style checks as warnings
|
||||
optStyleUsages, # only enforce consistent **usages** of the symbol
|
||||
optSkipSystemConfigFile, # skip the system's cfg/nims config file
|
||||
optSkipProjConfigFile, # skip the project's cfg/nims config file
|
||||
@@ -111,7 +110,6 @@ type # please make sure we have under 32 options
|
||||
optEnableDeepCopy # ORC specific: enable 'deepcopy' for all types.
|
||||
optShowNonExportedFields # for documentation: show fields that are not exported
|
||||
optJsBigInt64 # use bigints for 64-bit integers in JS
|
||||
optDocRaw # for documentation: Don't render markdown for JSON output
|
||||
optItaniumMangle # mangling follows the Itanium spec
|
||||
optCompress # turn on AST compression by converting it to NIF
|
||||
optWithinConfigSystem # we still compile within the configuration system
|
||||
@@ -157,6 +155,7 @@ type
|
||||
cmdCheck # semantic checking for whole project
|
||||
cmdM # only compile a single
|
||||
cmdParse # parse a single file (for debugging)
|
||||
cmdRod # .rod to some text representation (for debugging)
|
||||
cmdIdeTools # ide tools (e.g. nimsuggest)
|
||||
cmdNimscript # evaluate nimscript
|
||||
cmdDoc0
|
||||
@@ -195,7 +194,6 @@ type
|
||||
gcRegions = "regions"
|
||||
gcArc = "arc"
|
||||
gcOrc = "orc"
|
||||
gcYrc = "yrc" # thread-safe ORC (concurrent cycle collector)
|
||||
gcAtomicArc = "atomicArc"
|
||||
gcMarkAndSweep = "markAndSweep"
|
||||
gcHooks = "hooks"
|
||||
@@ -1048,9 +1046,6 @@ proc isDynlibOverride*(conf: ConfigRef; lib: string): bool =
|
||||
proc showNonExportedFields*(conf: ConfigRef) =
|
||||
incl(conf.globalOptions, optShowNonExportedFields)
|
||||
|
||||
proc docRawOutput*(conf: ConfigRef) =
|
||||
incl(conf.globalOptions, optDocRaw)
|
||||
|
||||
proc expandDone*(conf: ConfigRef): bool =
|
||||
result = conf.ideCmd == ideExpand and conf.expandLevels == 0 and conf.expandProgress
|
||||
|
||||
|
||||
@@ -148,6 +148,11 @@ proc processModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator;
|
||||
closeParser(p)
|
||||
if s.kind != llsStdIn: break
|
||||
closePasses(graph, a)
|
||||
if graph.config.backend notin {backendC, backendCpp, backendObjc}:
|
||||
# We only write rod files here if no C-like backend is active.
|
||||
# The C-like backends have been patched to support the IC mechanism.
|
||||
# They are responsible for closing the rod files. See `cbackend.nim`.
|
||||
closeRodFile(graph, module)
|
||||
result = true
|
||||
|
||||
proc compileModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags, fromModule: PSym = nil): PSym =
|
||||
@@ -163,10 +168,22 @@ proc compileModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags, fr
|
||||
elif graph.config.projectIsCmd: s = llStreamOpen(graph.config.cmdInput)
|
||||
discard processModule(graph, result, idGeneratorFromModule(result), s)
|
||||
if result == nil:
|
||||
result = newModule(graph, fileIdx)
|
||||
result.incl flags
|
||||
registerModule(graph, result)
|
||||
processModuleAux("import")
|
||||
var cachedModules: seq[FileIndex] = @[]
|
||||
result = moduleFromRodFile(graph, fileIdx, cachedModules)
|
||||
let filename = AbsoluteFile toFullPath(graph.config, fileIdx)
|
||||
if result == nil:
|
||||
result = newModule(graph, fileIdx)
|
||||
result.incl flags
|
||||
registerModule(graph, result)
|
||||
processModuleAux("import")
|
||||
else:
|
||||
if sfSystemModule in flags:
|
||||
graph.systemModule = result
|
||||
partialInitModule(result, graph, fileIdx, filename)
|
||||
for m in cachedModules:
|
||||
registerModuleById(graph, m)
|
||||
replayStateChanges(graph.packed.pm[m.int].module, graph)
|
||||
replayGenericCacheInformation(graph, m.int)
|
||||
elif graph.isDirty(result):
|
||||
result.excl sfDirty
|
||||
# reset module fields:
|
||||
|
||||
@@ -242,11 +242,8 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
raiseAssert "use setPipeLinePass to set a proper PipelinePass"
|
||||
|
||||
when not defined(nimKochBootstrap):
|
||||
# For cmdM: only write NIF for the main module, not for imported modules
|
||||
# (imported modules should be loaded from existing NIF files)
|
||||
let shouldWriteNif = (optCompress in graph.config.globalOptions) or
|
||||
(graph.config.cmd == cmdM and sfMainModule in module.flags)
|
||||
if shouldWriteNif and not graph.config.isDefined("nimscript"):
|
||||
if (optCompress in graph.config.globalOptions or graph.config.cmd == cmdM) and
|
||||
not graph.config.isDefined("nimscript"):
|
||||
topLevelStmts.add finalNode
|
||||
# Collect replay actions from both pragma computations and VM state diff
|
||||
var replayActions: seq[PNode] = @[]
|
||||
@@ -261,6 +258,12 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
|
||||
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog, replayActions)
|
||||
|
||||
if graph.config.backend notin {backendC, backendCpp, backendObjc} and graph.config.cmd != cmdM:
|
||||
# We only write rod files here if no C-like backend is active.
|
||||
# The C-like backends have been patched to support the IC mechanism.
|
||||
# They are responsible for closing the rod files. See `cbackend.nim`.
|
||||
# cmdM uses NIF files only, not ROD files.
|
||||
closeRodFile(graph, module)
|
||||
result = true
|
||||
|
||||
proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags; fromModule: PSym = nil): PSym =
|
||||
@@ -276,6 +279,7 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF
|
||||
elif graph.config.projectIsCmd: s = llStreamOpen(graph.config.cmdInput)
|
||||
discard processPipelineModule(graph, result, idGeneratorFromModule(result), s)
|
||||
if result == nil:
|
||||
var cachedModules: seq[FileIndex] = @[]
|
||||
when not defined(nimKochBootstrap):
|
||||
# For cmdM: load imports from NIF files (but compile the main module from source)
|
||||
# Skip when withinSystem is true (compiling system.nim itself)
|
||||
@@ -286,20 +290,13 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF
|
||||
let precomp = moduleFromNifFile(graph, fileIdx)
|
||||
if precomp.module == nil:
|
||||
let nifPath = toNifFilename(graph.config, fileIdx)
|
||||
globalError(graph.config, unknownLineInfo,
|
||||
localError(graph.config, unknownLineInfo,
|
||||
"nim m requires precompiled NIF for import: " & toFullPath(graph.config, fileIdx) &
|
||||
" (expected: " & nifPath & ")")
|
||||
return nil # Don't fall through to compile from source
|
||||
else:
|
||||
# Module successfully loaded from NIF file - use it and skip processing
|
||||
result = precomp.module
|
||||
if sfSystemModule in flags:
|
||||
graph.systemModule = result
|
||||
partialInitModule(result, graph, fileIdx, AbsoluteFile(toFullPath(graph.config, fileIdx)))
|
||||
# Replay state changes from the loaded NIF module
|
||||
if result.ast != nil:
|
||||
replayStateChanges(result, graph)
|
||||
return result # Return early, don't process from source
|
||||
if result == nil and graph.config.cmd != cmdM:
|
||||
# Fall back to ROD file loading (not used for cmdM which uses NIF only)
|
||||
result = moduleFromRodFile(graph, fileIdx, cachedModules)
|
||||
let path = toFullPath(graph.config, fileIdx)
|
||||
let filename = AbsoluteFile path
|
||||
# it could be a stdinfile/cmdfile
|
||||
@@ -318,6 +315,16 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF
|
||||
registerModule(graph, result)
|
||||
processModuleAux("import")
|
||||
partialInitModule(result, graph, fileIdx, filename)
|
||||
for m in cachedModules:
|
||||
registerModuleById(graph, m)
|
||||
if graph.config.cmd == cmdM:
|
||||
# cmdM uses NIF files - replay from module AST loaded by loadNifModule
|
||||
let module = graph.getModule(m)
|
||||
if module != nil and module.ast != nil:
|
||||
replayStateChanges(module, graph)
|
||||
else:
|
||||
replayStateChanges(graph.packed.pm[m.int].module, graph)
|
||||
replayGenericCacheInformation(graph, m.int)
|
||||
elif graph.isDirty(result):
|
||||
result.excl sfDirty
|
||||
# reset module fields:
|
||||
@@ -377,6 +384,7 @@ proc compilePipelineProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIdx
|
||||
connectPipelineCallbacks(graph)
|
||||
graph.config.m.systemFileIdx = fileInfoIdx(graph.config,
|
||||
graph.config.libpath / RelativeFile"system.nim")
|
||||
var cachedModules: seq[FileIndex] = @[]
|
||||
when not defined(nimKochBootstrap):
|
||||
let precomp = moduleFromNifFile(graph, graph.config.m.systemFileIdx)
|
||||
graph.systemModule = precomp.module
|
||||
|
||||
@@ -21,6 +21,8 @@ import std/[os, math, strutils]
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
from ic / ic import addCompilerProc
|
||||
|
||||
const
|
||||
FirstCallConv* = wNimcall
|
||||
LastCallConv* = wNoconv
|
||||
@@ -567,7 +569,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)
|
||||
@@ -765,6 +767,8 @@ proc markCompilerProc(c: PContext; s: PSym) =
|
||||
incl(s, sfCompilerProc)
|
||||
incl(s.flagsImpl, sfUsed)
|
||||
registerCompilerProc(c.graph, s)
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
addCompilerProc(c.encoder, c.packedRepr, s)
|
||||
|
||||
proc deprecatedStmt(c: PContext; outerPragma: PNode) =
|
||||
let pragma = outerPragma[1]
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
{.used.}
|
||||
|
||||
import
|
||||
lexer, options, idents, ast, msgs, lineinfos, wordrecg, trees
|
||||
lexer, options, idents, ast, msgs, lineinfos, wordrecg
|
||||
|
||||
import std/[strutils]
|
||||
|
||||
@@ -66,359 +66,6 @@ proc renderTree*(n: PNode, renderFlags: TRenderFlags = {}): string
|
||||
# determines how long the subtree will likely be, the second
|
||||
# phase appends to a buffer that will be the output.
|
||||
|
||||
type
|
||||
TPreferedDesc* = enum
|
||||
preferName, # default
|
||||
preferDesc, # probably should become what preferResolved is
|
||||
preferExported,
|
||||
preferModuleInfo, # fully qualified
|
||||
preferGenericArg,
|
||||
preferTypeName,
|
||||
preferResolved, # fully resolved symbols
|
||||
preferMixed,
|
||||
# most useful, shows: symbol + resolved symbols if it differs, e.g.:
|
||||
# tuple[a: MyInt{int}, b: float]
|
||||
preferInlayHint,
|
||||
preferInferredEffects,
|
||||
|
||||
proc typeToString*(typ: PType; prefer: TPreferedDesc = preferName): string
|
||||
template `$`*(typ: PType): string = typeToString(typ)
|
||||
|
||||
proc valueToString(a: PNode): string =
|
||||
case a.kind
|
||||
of nkCharLit, nkUIntLit..nkUInt64Lit:
|
||||
result = $cast[uint64](a.intVal)
|
||||
of nkIntLit..nkInt64Lit:
|
||||
result = $a.intVal
|
||||
of nkFloatLit..nkFloat128Lit: result = $a.floatVal
|
||||
of nkStrLit..nkTripleStrLit: result = a.strVal
|
||||
of nkStaticExpr: result = "static(" & a[0].renderTree & ")"
|
||||
else: result = "<invalid value>"
|
||||
|
||||
proc rangeToStr(n: PNode): string =
|
||||
assert(n.kind == nkRange)
|
||||
result = valueToString(n[0]) & ".." & valueToString(n[1])
|
||||
|
||||
const preferToResolveSymbols = {preferName, preferTypeName, preferModuleInfo,
|
||||
preferGenericArg, preferResolved, preferMixed, preferInlayHint, preferInferredEffects}
|
||||
|
||||
|
||||
const
|
||||
typeToStr: array[TTypeKind, string] = ["None", "bool", "char", "empty",
|
||||
"Alias", "typeof(nil)", "untyped", "typed", "typeDesc",
|
||||
# xxx typeDesc=>typedesc: typedesc is declared as such, and is 10x more common.
|
||||
"GenericInvocation", "GenericBody", "GenericInst", "GenericParam",
|
||||
"distinct $1", "enum", "ordinal[$1]", "array[$1, $2]", "object", "tuple",
|
||||
"set[$1]", "range[$1]", "ptr ", "ref ", "var ", "seq[$1]", "proc",
|
||||
"pointer", "OpenArray[$1]", "string", "cstring", "Forward",
|
||||
"int", "int8", "int16", "int32", "int64",
|
||||
"float", "float32", "float64", "float128",
|
||||
"uint", "uint8", "uint16", "uint32", "uint64",
|
||||
"owned", "sink",
|
||||
"lent ", "varargs[$1]", "UncheckedArray[$1]", "Error Type",
|
||||
"BuiltInTypeClass", "UserTypeClass",
|
||||
"UserTypeClassInst", "CompositeTypeClass", "inferred",
|
||||
"and", "or", "not", "any", "static", "TypeFromExpr", "concept", # xxx bugfix
|
||||
"void", "iterable"]
|
||||
|
||||
proc addTypeFlags(name: var string, typ: PType) {.inline.} =
|
||||
if tfNotNil in typ.flags: name.add(" not nil")
|
||||
|
||||
proc isIntLit*(t: PType): bool {.inline.} =
|
||||
result = t.kind == tyInt and t.n != nil and t.n.kind == nkIntLit
|
||||
|
||||
proc isFloatLit*(t: PType): bool {.inline.} =
|
||||
result = t.kind == tyFloat and t.n != nil and t.n.kind == nkFloatLit
|
||||
|
||||
# TODO: It would be a good idea to kill the special state of a resolved
|
||||
# concept by switching to tyAlias within the instantiated procs.
|
||||
# Currently, tyAlias is always skipped with skipModifier, which means that
|
||||
# we can store information about the matched concept in another position.
|
||||
# Then builtInFieldAccess can be modified to properly read the derived
|
||||
# consts and types stored within the concept.
|
||||
template isResolvedUserTypeClass*(t: PType): bool =
|
||||
tfResolved in t.flags
|
||||
|
||||
proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
|
||||
let preferToplevel = prefer
|
||||
proc getPrefer(prefer: TPreferedDesc): TPreferedDesc =
|
||||
if preferToplevel in {preferResolved, preferMixed}:
|
||||
preferToplevel # sticky option
|
||||
else:
|
||||
prefer
|
||||
|
||||
proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
|
||||
result = ""
|
||||
let prefer = getPrefer(prefer)
|
||||
let t = typ
|
||||
if t == nil: return
|
||||
if prefer in preferToResolveSymbols and t.sym != nil and
|
||||
sfAnon notin t.sym.flags and t.kind notin {tySequence, tyInferred}:
|
||||
if t.kind == tyInt and isIntLit(t):
|
||||
if prefer == preferInlayHint:
|
||||
result = t.sym.name.s
|
||||
else:
|
||||
result = t.sym.name.s & " literal(" & $t.n.intVal & ")"
|
||||
elif t.kind == tyAlias and t.elementType.kind != tyAlias:
|
||||
result = typeToString(t.elementType)
|
||||
elif prefer in {preferResolved, preferMixed}:
|
||||
case t.kind
|
||||
of IntegralTypes + {tyFloat..tyFloat128} + {tyString, tyCstring}:
|
||||
result = typeToStr[t.kind]
|
||||
of tyGenericBody:
|
||||
result = typeToString(t.last)
|
||||
of tyCompositeTypeClass:
|
||||
# avoids showing `A[any]` in `proc fun(a: A)` with `A = object[T]`
|
||||
result = typeToString(t.last.last)
|
||||
else:
|
||||
result = t.sym.name.s
|
||||
if prefer == preferMixed and result != t.sym.name.s:
|
||||
result = t.sym.name.s & "{" & result & "}"
|
||||
elif prefer in {preferName, preferTypeName, preferInlayHint, preferInferredEffects} or t.sym.owner.isNil:
|
||||
# note: should probably be: {preferName, preferTypeName, preferGenericArg}
|
||||
result = t.sym.name.s
|
||||
if t.kind == tyGenericParam and t.genericParamHasConstraints:
|
||||
result.add ": "
|
||||
result.add t.elementType.typeToString
|
||||
else:
|
||||
result = t.sym.owner.name.s & '.' & t.sym.name.s
|
||||
result.addTypeFlags(t)
|
||||
return
|
||||
case t.kind
|
||||
of tyInt:
|
||||
if not isIntLit(t) or prefer == preferExported:
|
||||
result = typeToStr[t.kind]
|
||||
else:
|
||||
case prefer:
|
||||
of preferGenericArg:
|
||||
result = $t.n.intVal
|
||||
of preferInlayHint:
|
||||
result = "int"
|
||||
else:
|
||||
result = "int literal(" & $t.n.intVal & ")"
|
||||
of tyGenericInst:
|
||||
result = typeToString(t.genericHead) & '['
|
||||
for needsComma, a in t.genericInstParams:
|
||||
if needsComma: result.add(", ")
|
||||
result.add(typeToString(a, preferGenericArg))
|
||||
result.add(']')
|
||||
of tyGenericInvocation:
|
||||
result = typeToString(t.genericHead) & '['
|
||||
for needsComma, a in t.genericInvocationParams:
|
||||
if needsComma: result.add(", ")
|
||||
result.add(typeToString(a, preferGenericArg))
|
||||
result.add(']')
|
||||
of tyGenericBody:
|
||||
result = typeToString(t.typeBodyImpl) & '['
|
||||
for i, a in t.genericBodyParams:
|
||||
if i > 0: result.add(", ")
|
||||
result.add(typeToString(a, preferTypeName))
|
||||
result.add(']')
|
||||
of tyTypeDesc:
|
||||
if t.elementType.kind == tyNone: result = "typedesc"
|
||||
else: result = "typedesc[" & typeToString(t.elementType) & "]"
|
||||
of tyStatic:
|
||||
if prefer == preferGenericArg and t.n != nil:
|
||||
result = t.n.renderTree
|
||||
else:
|
||||
result = "static[" & (if t.hasElementType: typeToString(t.skipModifier) else: "") & "]"
|
||||
if t.n != nil: result.add "(" & renderTree(t.n) & ")"
|
||||
of tyUserTypeClass:
|
||||
if t.sym != nil and t.sym.owner != nil:
|
||||
if t.isResolvedUserTypeClass: return typeToString(t.last)
|
||||
return t.sym.owner.name.s
|
||||
else:
|
||||
result = "<invalid tyUserTypeClass>"
|
||||
of tyBuiltInTypeClass:
|
||||
result =
|
||||
case t.base.kind
|
||||
of tyVar: "var"
|
||||
of tyRef: "ref"
|
||||
of tyPtr: "ptr"
|
||||
of tySequence: "seq"
|
||||
of tyArray: "array"
|
||||
of tySet: "set"
|
||||
of tyRange: "range"
|
||||
of tyDistinct: "distinct"
|
||||
of tyProc: "proc"
|
||||
of tyObject: "object"
|
||||
of tyTuple: "tuple"
|
||||
of tyOpenArray: "openArray"
|
||||
else: typeToStr[t.base.kind]
|
||||
of tyInferred:
|
||||
let concrete = t.previouslyInferred
|
||||
if concrete != nil: result = typeToString(concrete)
|
||||
else: result = "inferred[" & typeToString(t.base) & "]"
|
||||
of tyUserTypeClassInst:
|
||||
let body = t.base
|
||||
result = body.sym.name.s & "["
|
||||
for needsComma, a in t.userTypeClassInstParams:
|
||||
if needsComma: result.add(", ")
|
||||
result.add(typeToString(a))
|
||||
result.add "]"
|
||||
of tyAnd:
|
||||
for i, son in t.ikids:
|
||||
if i > 0: result.add(" and ")
|
||||
result.add(typeToString(son))
|
||||
of tyOr:
|
||||
for i, son in t.ikids:
|
||||
if i > 0: result.add(" or ")
|
||||
result.add(typeToString(son))
|
||||
of tyNot:
|
||||
result = "not " & typeToString(t.elementType)
|
||||
of tyUntyped:
|
||||
#internalAssert t.len == 0
|
||||
result = "untyped"
|
||||
of tyFromExpr:
|
||||
if t.n == nil:
|
||||
result = "unknown"
|
||||
else:
|
||||
result = "typeof(" & renderTree(t.n) & ")"
|
||||
of tyArray:
|
||||
result = "array"
|
||||
if t.hasElementType:
|
||||
if t.indexType.kind == tyRange:
|
||||
result &= "[" & rangeToStr(t.indexType.n) & ", " &
|
||||
typeToString(t.elementType) & ']'
|
||||
else:
|
||||
result &= "[" & typeToString(t.indexType) & ", " &
|
||||
typeToString(t.elementType) & ']'
|
||||
of tyUncheckedArray:
|
||||
result = "UncheckedArray"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.elementType) & ']'
|
||||
of tySequence:
|
||||
if t.sym != nil and prefer != preferResolved:
|
||||
result = t.sym.name.s
|
||||
else:
|
||||
result = "seq"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.elementType) & ']'
|
||||
of tyOrdinal:
|
||||
result = "ordinal"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.skipModifier) & ']'
|
||||
of tySet:
|
||||
result = "set"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.elementType) & ']'
|
||||
of tyOpenArray:
|
||||
result = "openArray"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.elementType) & ']'
|
||||
of tyDistinct:
|
||||
result = "distinct " & typeToString(t.elementType,
|
||||
if prefer == preferModuleInfo: preferModuleInfo else: preferTypeName)
|
||||
of tyIterable:
|
||||
# xxx factor this pattern
|
||||
result = "iterable"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.skipModifier) & ']'
|
||||
of tyTuple:
|
||||
# we iterate over t.sons here, because t.n may be nil
|
||||
if t.n != nil:
|
||||
result = "tuple["
|
||||
for i in 0..<t.n.len:
|
||||
assert(t.n[i].kind == nkSym)
|
||||
result.add(t.n[i].sym.name.s & ": " & typeToString(t.n[i].sym.typ))
|
||||
if i < t.n.len - 1: result.add(", ")
|
||||
result.add(']')
|
||||
elif t.isEmptyTupleType:
|
||||
result = "tuple[]"
|
||||
elif t.isSingletonTupleType:
|
||||
result = "("
|
||||
for son in t.kids:
|
||||
result.add(typeToString(son))
|
||||
result.add(",)")
|
||||
else:
|
||||
result = "("
|
||||
for i, son in t.ikids:
|
||||
if i > 0: result.add ", "
|
||||
result.add(typeToString(son))
|
||||
result.add(')')
|
||||
of tyPtr, tyRef, tyVar, tyLent:
|
||||
result = if isOutParam(t): "out " else: typeToStr[t.kind]
|
||||
result.add typeToString(t.elementType)
|
||||
of tyRange:
|
||||
result = "range "
|
||||
if t.n != nil and t.n.kind == nkRange:
|
||||
result.add rangeToStr(t.n)
|
||||
if prefer != preferExported:
|
||||
result.add("(" & typeToString(t.elementType) & ")")
|
||||
of tyProc:
|
||||
result = if tfIterator in t.flags: "iterator "
|
||||
elif t.owner != nil:
|
||||
case t.owner.kind
|
||||
of skTemplate: "template "
|
||||
of skMacro: "macro "
|
||||
of skConverter: "converter "
|
||||
else: "proc "
|
||||
else:
|
||||
"proc "
|
||||
if tfUnresolved in t.flags: result.add "[*missing parameters*]"
|
||||
result.add "("
|
||||
for i, a in t.paramTypes:
|
||||
if i > FirstParamAt: result.add(", ")
|
||||
let j = paramTypeToNodeIndex(i)
|
||||
if t.n != nil and j < t.n.len and t.n[j].kind == nkSym:
|
||||
result.add(t.n[j].sym.name.s)
|
||||
result.add(": ")
|
||||
result.add(typeToString(a))
|
||||
result.add(')')
|
||||
if t.returnType != nil: result.add(": " & typeToString(t.returnType))
|
||||
var prag = if t.callConv == ccNimCall and tfExplicitCallConv notin t.flags: "" else: $t.callConv
|
||||
var hasImplicitRaises = false
|
||||
if not isNil(t.owner) and not isNil(t.owner.ast) and (t.owner.ast.len - 1) >= pragmasPos:
|
||||
let pragmasNode = t.owner.ast[pragmasPos]
|
||||
let raisesSpec = effectSpec(pragmasNode, wRaises)
|
||||
if not isNil(raisesSpec):
|
||||
addSep(prag)
|
||||
prag.add("raises: ")
|
||||
prag.add(renderTree raisesSpec)
|
||||
hasImplicitRaises = true
|
||||
if tfNoSideEffect in t.flags:
|
||||
addSep(prag)
|
||||
prag.add("noSideEffect")
|
||||
if tfThread in t.flags:
|
||||
addSep(prag)
|
||||
prag.add("gcsafe")
|
||||
var effectsOfStr = ""
|
||||
for i, a in t.paramTypes:
|
||||
let j = paramTypeToNodeIndex(i)
|
||||
if t.n != nil and j < t.n.len and t.n[j].kind == nkSym and t.n[j].sym.kind == skParam and sfEffectsDelayed in t.n[j].sym.flags:
|
||||
addSep(effectsOfStr)
|
||||
effectsOfStr.add(t.n[j].sym.name.s)
|
||||
if effectsOfStr != "":
|
||||
addSep(prag)
|
||||
prag.add("effectsOf: ")
|
||||
prag.add(effectsOfStr)
|
||||
if not hasImplicitRaises and prefer == preferInferredEffects and not isNil(t.owner) and not isNil(t.owner.typ) and not isNil(t.owner.typ.n) and (t.owner.typ.n.len > 0):
|
||||
let effects = t.n[0]
|
||||
if effects.kind == nkEffectList and effects.len == effectListLen:
|
||||
var inferredRaisesStr = ""
|
||||
let effs = effects[exceptionEffects]
|
||||
if not isNil(effs):
|
||||
for eff in items(effs):
|
||||
if not isNil(eff):
|
||||
addSep(inferredRaisesStr)
|
||||
inferredRaisesStr.add($eff.typ)
|
||||
addSep(prag)
|
||||
prag.add("raises: <inferred> [")
|
||||
prag.add(inferredRaisesStr)
|
||||
prag.add("]")
|
||||
if prag.len != 0: result.add("{." & prag & ".}")
|
||||
of tyVarargs:
|
||||
result = typeToStr[t.kind] % typeToString(t.elementType)
|
||||
of tySink:
|
||||
result = "sink " & typeToString(t.skipModifier)
|
||||
of tyOwned:
|
||||
result = "owned " & typeToString(t.elementType)
|
||||
else:
|
||||
result = typeToStr[t.kind]
|
||||
result.addTypeFlags(t)
|
||||
result = typeToString(typ, prefer)
|
||||
|
||||
|
||||
proc disamb(g: var TSrcGen; s: PSym): int =
|
||||
# we group by 's.name.s' to compute the stable name ID.
|
||||
result = 0
|
||||
@@ -1215,28 +862,10 @@ proc genSymSuffix(result: var string, s: PSym) {.inline.} =
|
||||
result.add '_'
|
||||
result.addInt s.id
|
||||
|
||||
proc gsemmedParams(g: var TSrcGen, n: PNode) =
|
||||
put(g, tkParLe, "(")
|
||||
for i in 1..<n.len:
|
||||
if i > 1:
|
||||
putWithSpace(g, tkComma, ";")
|
||||
let x {.cursor.} = n[i]
|
||||
if x.kind == nkSym:
|
||||
put g, tkSymbol, renderDefinitionName(x.sym)
|
||||
putWithSpace(g, tkColon, ":")
|
||||
put g, tkSymbol, typeToString(x.sym.typ)
|
||||
else:
|
||||
gsub(g, x)
|
||||
put(g, tkParRi, ")")
|
||||
if not isEmptyType(n[0].typ):
|
||||
putWithSpace(g, tkColon, ":")
|
||||
gsub(g, n[0])
|
||||
|
||||
proc gproc(g: var TSrcGen, n: PNode) =
|
||||
var c: TContext = initContext()
|
||||
var s: PSym = nil
|
||||
if n[namePos].kind == nkSym:
|
||||
s = n[namePos].sym
|
||||
let s = n[namePos].sym
|
||||
var ret = renderDefinitionName(s)
|
||||
ret.genSymSuffix(s)
|
||||
put(g, tkSymbol, ret)
|
||||
@@ -1251,10 +880,7 @@ proc gproc(g: var TSrcGen, n: PNode) =
|
||||
gsub(g, n[miscPos][1])
|
||||
else:
|
||||
gsub(g, n[genericParamsPos])
|
||||
if n[paramsPos].len == 0 and s != nil and s.typ != nil and s.typ.n != nil:
|
||||
gsemmedParams(g, s.typ.n)
|
||||
else:
|
||||
gsub(g, n[paramsPos])
|
||||
gsub(g, n[paramsPos])
|
||||
if renderNoPragmas notin g.flags:
|
||||
gsub(g, n[pragmasPos])
|
||||
if renderNoBody notin g.flags:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -889,6 +889,8 @@ proc semWithPContext*(c: PContext, n: PNode): PNode =
|
||||
else:
|
||||
result = newNodeI(nkEmpty, n.info)
|
||||
#if c.config.cmd == cmdIdeTools: findSuggest(c, n)
|
||||
storeRodNode(c, result)
|
||||
|
||||
|
||||
proc reportUnusedModules(c: PContext) =
|
||||
if c.config.cmd == cmdM: return
|
||||
|
||||
@@ -830,21 +830,6 @@ proc inheritBindings(c: PContext, x: var TCandidate, expectedType: PType) =
|
||||
for i in 0 ..< flatUnbound.len():
|
||||
x.bindings.put(flatUnbound[i], flatBound[i])
|
||||
|
||||
proc compactVoidArgs(n: PNode): PNode =
|
||||
# deletes void args from the argument list, which are created by `setSon`
|
||||
var hasNil = false
|
||||
for i in 0..<n.len:
|
||||
if n[i] == nil:
|
||||
hasNil = true
|
||||
break
|
||||
if not hasNil:
|
||||
result = n
|
||||
else:
|
||||
result = copyNode(n)
|
||||
for i in 0..<n.len:
|
||||
if n[i] != nil:
|
||||
result.add n[i]
|
||||
|
||||
proc semResolvedCall(c: PContext, x: var TCandidate,
|
||||
n: PNode, flags: TExprFlags;
|
||||
expectedType: PType = nil): PNode =
|
||||
@@ -895,7 +880,7 @@ proc semResolvedCall(c: PContext, x: var TCandidate,
|
||||
markUsed(c, info, finalCallee, isGenericInstance = true)
|
||||
onUse(info, finalCallee, isGenericInstance = true)
|
||||
|
||||
result = compactVoidArgs(x.call)
|
||||
result = x.call
|
||||
instGenericConvertersSons(c, result, x)
|
||||
markConvertersUsed(c, result)
|
||||
result[0] = newSymNode(finalCallee, getCallLineInfo(result[0]))
|
||||
|
||||
@@ -19,6 +19,8 @@ import
|
||||
magicsys, vmdef, modulegraphs, lineinfos, pathutils, layeredtable,
|
||||
types, lowerings, trees, parampatterns, astalgo
|
||||
|
||||
import ic / ic
|
||||
|
||||
type
|
||||
TOptionEntry* = object # entries to put on a stack for pragma parsing
|
||||
options*: TOptions
|
||||
@@ -334,14 +336,28 @@ proc newContext*(graph: ModuleGraph; module: PSym): PContext =
|
||||
signatures: initStrTable(),
|
||||
features: graph.config.features
|
||||
)
|
||||
if graph.config.symbolFiles != disabledSf:
|
||||
let id = module.position
|
||||
if graph.config.cmd != cmdM:
|
||||
assert graph.packed[id].status in {undefined, outdated}
|
||||
graph.packed[id].status = storing
|
||||
graph.packed[id].module = module
|
||||
initEncoder graph, module
|
||||
|
||||
template packedRepr*(c): untyped = c.graph.packed[c.module.position].fromDisk
|
||||
template encoder*(c): untyped = c.graph.encoders[c.module.position]
|
||||
|
||||
proc addIncludeFileDep*(c: PContext; f: FileIndex) =
|
||||
discard
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
addIncludeFileDep(c.encoder, c.packedRepr, f)
|
||||
|
||||
proc addImportFileDep*(c: PContext; f: FileIndex) =
|
||||
discard
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
addImportFileDep(c.encoder, c.packedRepr, f)
|
||||
|
||||
proc addPragmaComputation*(c: PContext; n: PNode) =
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
addPragmaComputation(c.encoder, c.packedRepr, n)
|
||||
# Also store for NIF-based IC (cmdM mode or optCompress)
|
||||
if optCompress in c.config.globalOptions or c.config.cmd == cmdM:
|
||||
addNifReplayAction(c.graph, c.module.position.int32, n)
|
||||
@@ -352,28 +368,38 @@ proc inclSym(sq: var seq[PSym], s: PSym): bool =
|
||||
sq.add s
|
||||
result = true
|
||||
|
||||
proc addConverter*(c: PContext, conv: PSym) =
|
||||
assert conv != nil
|
||||
if inclSym(c.converters, conv):
|
||||
proc addConverter*(c: PContext, conv: LazySym) =
|
||||
assert conv.sym != nil
|
||||
if inclSym(c.converters, conv.sym):
|
||||
add(c.graph.ifaces[c.module.position].converters, conv)
|
||||
|
||||
proc addConverterDef*(c: PContext, conv: PSym) =
|
||||
proc addConverterDef*(c: PContext, conv: LazySym) =
|
||||
addConverter(c, conv)
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
addConverter(c.encoder, c.packedRepr, conv.sym)
|
||||
|
||||
proc addPureEnum*(c: PContext, e: PSym) =
|
||||
assert e != nil
|
||||
proc addPureEnum*(c: PContext, e: LazySym) =
|
||||
assert e.sym != nil
|
||||
add(c.graph.ifaces[c.module.position].pureEnums, e)
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
addPureEnum(c.encoder, c.packedRepr, e.sym)
|
||||
|
||||
proc addPattern*(c: PContext, p: PSym) =
|
||||
assert p != nil
|
||||
if inclSym(c.patterns, p):
|
||||
proc addPattern*(c: PContext, p: LazySym) =
|
||||
assert p.sym != nil
|
||||
if inclSym(c.patterns, p.sym):
|
||||
add(c.graph.ifaces[c.module.position].patterns, p)
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
addTrmacro(c.encoder, c.packedRepr, p.sym)
|
||||
|
||||
proc exportSym*(c: PContext; s: PSym) =
|
||||
strTableAdds(c.graph, c.module, s)
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
addExported(c.encoder, c.packedRepr, s)
|
||||
|
||||
proc reexportSym*(c: PContext; s: PSym) =
|
||||
strTableAdds(c.graph, c.module, s)
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
addReexport(c.encoder, c.packedRepr, s)
|
||||
|
||||
proc newLib*(kind: TLibKind): PLib =
|
||||
result = PLib(kind: kind) #result.syms = initObjectSet()
|
||||
@@ -588,11 +614,19 @@ template addExport*(c: PContext; s: PSym) =
|
||||
## convenience to export a symbol from the current module
|
||||
addExport(c.graph, c.module, s)
|
||||
|
||||
proc storeRodNode*(c: PContext, n: PNode) =
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
toPackedNodeTopLevel(n, c.encoder, c.packedRepr)
|
||||
|
||||
proc addToGenericProcCache*(c: PContext; s: PSym; inst: PInstantiation) =
|
||||
c.graph.procInstCache.mgetOrPut(s.itemId, @[]).add inst
|
||||
c.graph.procInstCache.mgetOrPut(s.itemId, @[]).add LazyInstantiation(module: c.module.position, inst: inst)
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
storeInstantiation(c.encoder, c.packedRepr, s, inst)
|
||||
|
||||
proc addToGenericCache*(c: PContext; s: PSym; inst: PType) =
|
||||
c.graph.typeInstCache.mgetOrPut(s.itemId, @[]).add inst
|
||||
c.graph.typeInstCache.mgetOrPut(s.itemId, @[]).add LazyType(typ: inst)
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
storeTypeInst(c.encoder, c.packedRepr, s, inst)
|
||||
|
||||
proc sealRodFile*(c: PContext) =
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
@@ -608,8 +642,9 @@ proc rememberExpansion*(c: PContext; info: TLineInfo; expandedSym: PSym) =
|
||||
## in the sem'checked AST. This is very bad for IDE-like tooling
|
||||
## ("find all usages of this template" would not work). We need special
|
||||
## logic to remember macro/template expansions. This is done here and
|
||||
## delegated to the "NIF" file mechanism.
|
||||
discard "XXX To implement"
|
||||
## delegated to the "rod" file mechanism.
|
||||
if c.config.symbolFiles != disabledSf:
|
||||
storeExpansion(c.encoder, c.packedRepr, info, expandedSym)
|
||||
|
||||
const
|
||||
errVarForOutParamNeededX = "for a 'var' type a variable needs to be passed; but '$1' is immutable"
|
||||
@@ -728,10 +763,10 @@ proc analyseIfAddressTakenInCall*(c: PContext, n: PNode, isConverter = false) =
|
||||
proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode =
|
||||
## Replaces builtin generic hooks with lifted hooks.
|
||||
case kind
|
||||
of attachedDestructor, attachedDispose:
|
||||
of attachedDestructor:
|
||||
result = n
|
||||
let t = n[1].typ.skipTypes(abstractVar)
|
||||
let op = getAttachedOp(c.graph, t, kind)
|
||||
let op = getAttachedOp(c.graph, t, attachedDestructor)
|
||||
if op != nil:
|
||||
result[0] = newSymNode(op)
|
||||
if op.typ != nil and op.typ.len == 2 and op.typ.firstParamType.kind != tyVar:
|
||||
|
||||
@@ -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
|
||||
@@ -832,6 +832,9 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PTyp
|
||||
proc fixAbstractType(c: PContext, n: PNode) =
|
||||
for i in 1..<n.len:
|
||||
let it = n[i]
|
||||
if it == nil:
|
||||
localError(c.config, n.info, "'$1' has nil child at index $2" % [renderTree(n, {renderNoComments}), $i])
|
||||
return
|
||||
# do not get rid of nkHiddenSubConv for OpenArrays, the codegen needs it:
|
||||
if it.kind == nkHiddenSubConv and
|
||||
skipTypes(it.typ, abstractVar).kind notin {tyOpenArray, tyVarargs}:
|
||||
@@ -1152,7 +1155,7 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
|
||||
localError(c.config, n.info, msg)
|
||||
return errorNode(c, n)
|
||||
else:
|
||||
result = compactVoidArgs(m.call)
|
||||
result = m.call
|
||||
instGenericConvertersSons(c, result, m)
|
||||
markConvertersUsed(c, result)
|
||||
|
||||
@@ -1658,9 +1661,6 @@ proc semDeref(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||
n[0] = a
|
||||
result = n
|
||||
var t = skipTypes(n[0].typ, {tyGenericInst, tyVar, tyLent, tyAlias, tySink, tyOwned})
|
||||
if t.kind == tyTypeDesc:
|
||||
localError(c.config, n.info, "missing generic parameter")
|
||||
return nil
|
||||
case t.kind
|
||||
of tyRef, tyPtr: n.typ = t.elementType
|
||||
of tyMetaTypes, tyFromExpr:
|
||||
@@ -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)
|
||||
@@ -3013,9 +3013,9 @@ proc semExportExcept(c: PContext, n: PNode): PNode =
|
||||
|
||||
proc semExport(c: PContext, n: PNode): PNode =
|
||||
proc specialSyms(c: PContext; s: PSym) {.inline.} =
|
||||
if s.kind == skConverter: addConverter(c, s)
|
||||
if s.kind == skConverter: addConverter(c, LazySym(sym: s))
|
||||
elif s.kind == skType and s.typ != nil and s.typ.kind == tyEnum and sfPure in s.flags:
|
||||
addPureEnum(c, s)
|
||||
addPureEnum(c, LazySym(sym: s))
|
||||
|
||||
result = newNodeI(nkExportStmt, n.info)
|
||||
for i in 0..<n.len:
|
||||
|
||||
@@ -84,8 +84,6 @@ type
|
||||
gcUnsafe, isRecursive, isTopLevel, hasSideEffect, inEnforcedGcSafe: bool
|
||||
isInnerProc: bool
|
||||
inEnforcedNoSideEffects: bool
|
||||
isArrayIndexing: bool
|
||||
currentExceptType: PType
|
||||
unknownRaises: seq[(PSym, TLineInfo)]
|
||||
currOptions: TOptions
|
||||
optionsStack: seq[(TOptions, TNoteKinds)]
|
||||
@@ -149,37 +147,6 @@ proc isLocalSym(a: PEffects, s: PSym): bool =
|
||||
s.typ != nil and (s.kind in {skLet, skVar, skResult} or (s.kind == skParam and isOutParam(s.typ))) and
|
||||
sfGlobal notin s.flags and s.owner == a.owner
|
||||
|
||||
proc isRangeSupertype(conf: ConfigRef; wider, narrower: PType): bool =
|
||||
## Check if `wider` type fully contains `narrower` type
|
||||
## Returns true if narrower fits entirely within wider (safe conversion)
|
||||
if wider.isOrdinalType:
|
||||
let wideFirst = firstOrd(conf, wider)
|
||||
let wideLast = lastOrd(conf, wider)
|
||||
let narrowFirst = firstOrd(conf, narrower)
|
||||
let narrowLast = lastOrd(conf, narrower)
|
||||
result = narrowFirst >= wideFirst and narrowLast <= wideLast
|
||||
elif not narrower.isOrdinalType:
|
||||
let wideFirst = firstFloat(wider)
|
||||
let wideLast = lastFloat(wider)
|
||||
let narrowFirst = firstFloat(narrower)
|
||||
let narrowLast = lastFloat(narrower)
|
||||
result = narrowFirst >= wideFirst and narrowLast <= wideLast
|
||||
else:
|
||||
# int -> float ranges; warn
|
||||
result = false
|
||||
|
||||
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})
|
||||
let a = argType.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct})
|
||||
if f.kind == tyRange:
|
||||
# Only warn if formal range doesn't fully contain argument range
|
||||
# Check if the ranges don't perfectly overlap
|
||||
result = not isRangeSupertype(conf, f, a)
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc lockLocations(a: PEffects; pragma: PNode) =
|
||||
if pragma.kind != nkExprColonExpr:
|
||||
localError(a.config, pragma.info, "locks pragma without argument")
|
||||
@@ -610,25 +577,11 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
|
||||
let b = n[i]
|
||||
if b.kind == nkExceptBranch:
|
||||
setLen(tracked.init, oldState)
|
||||
# If this except branch catches exactly one type, record it so an
|
||||
# empty `raise` inside the branch can be inferred as re-raising that
|
||||
# specific exception type instead of the generic `Exception`.
|
||||
var savedExcept: PType = tracked.currentExceptType
|
||||
var inferredExcept: PType = nil
|
||||
if b.len == 2:
|
||||
if b[0].isInfixAs():
|
||||
assert(b[0][1].kind == nkType)
|
||||
inferredExcept = b[0][1].typ
|
||||
else:
|
||||
assert(b[0].kind == nkType)
|
||||
inferredExcept = b[0].typ
|
||||
tracked.currentExceptType = inferredExcept
|
||||
for j in 0..<b.len - 1:
|
||||
if b[j].isInfixAs(): # skips initialization checks
|
||||
assert(b[j][2].kind == nkSym)
|
||||
tracked.init.add b[j][2].sym.id
|
||||
track(tracked, b[^1])
|
||||
tracked.currentExceptType = savedExcept
|
||||
for i in oldState..<tracked.init.len:
|
||||
addToIntersection(inter, tracked.init[i], bsNone)
|
||||
else:
|
||||
@@ -1311,14 +1264,7 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
# A `raise` with no arguments means we're going to re-raise the exception
|
||||
# being handled or, if outside of an `except` block, a `ReraiseDefect`.
|
||||
# Here we add a `Exception` tag in order to cover both the cases.
|
||||
if tracked.currentExceptType != nil:
|
||||
var en = newNode(nkType)
|
||||
en.typ = tracked.currentExceptType
|
||||
en.info = n.info
|
||||
addRaiseEffect(tracked, en, nil)
|
||||
createTypeBoundOps(tracked, tracked.currentExceptType, n.info)
|
||||
else:
|
||||
addRaiseEffect(tracked, createRaise(tracked.graph, n), nil)
|
||||
addRaiseEffect(tracked, createRaise(tracked.graph, n), nil)
|
||||
of nkCallKinds:
|
||||
trackCall(tracked, n)
|
||||
of nkDotExpr:
|
||||
@@ -1536,11 +1482,6 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
message(tracked.config, n.info, warnPtrToCstringConv,
|
||||
$n[1].typ)
|
||||
|
||||
# Check for implicit range conversions
|
||||
if n.kind == nkHiddenStdConv and (not tracked.isArrayIndexing) and
|
||||
shouldWarnRangeConversion(tracked.config, n.typ, n[1].typ):
|
||||
message(tracked.config, n.info, warnImplicitRangeConversion,
|
||||
typeToString(n[1].typ) & " -> " & typeToString(n.typ))
|
||||
|
||||
let t = n.typ.skipTypes(abstractInst)
|
||||
if t.kind == tyEnum:
|
||||
@@ -1579,12 +1520,7 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
checkBounds(tracked, n[0], n[1])
|
||||
track(tracked, n[0])
|
||||
dec tracked.leftPartOfAsgn
|
||||
for i in 1 ..< n.len:
|
||||
if i == 1:
|
||||
tracked.isArrayIndexing = true
|
||||
track(tracked, n[i])
|
||||
if i == 1:
|
||||
tracked.isArrayIndexing = false
|
||||
for i in 1 ..< n.len: track(tracked, n[i])
|
||||
inc tracked.leftPartOfAsgn
|
||||
of nkError:
|
||||
localError(tracked.config, n.info, errorToString(tracked.config, n))
|
||||
@@ -1756,7 +1692,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:
|
||||
|
||||
@@ -1845,13 +1845,6 @@ proc typeSectionFinalPass(c: PContext, n: PNode) =
|
||||
let baseType = s.typ.safeSkipTypes(abstractPtrs)
|
||||
if baseType.kind in {tyObject, tyTuple} and not baseType.n.isNil:
|
||||
checkForMetaFields(c, baseType.n, hasError)
|
||||
|
||||
if s.typ.kind in {tySet, tyArray, tySequence, tyUncheckedArray} and s.typ.elementType.kind == tyNone:
|
||||
# magic generics are not filled but tyNone is added to its elements by default,
|
||||
# we lift them to tyBuiltInTypeClass here
|
||||
s.typ = newTypeS(tyBuiltInTypeClass, c,
|
||||
newTypeS(s.typ.kind, c))
|
||||
|
||||
if not hasError:
|
||||
checkConstructedType(c.config, s.info, s.typ)
|
||||
#instAllTypeBoundOp(c, n.info)
|
||||
@@ -2175,7 +2168,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
|
||||
@@ -2783,7 +2776,7 @@ proc semConverterDef(c: PContext, n: PNode): PNode =
|
||||
var t = s.typ
|
||||
if t.returnType == nil: localError(c.config, n.info, errXNeedsReturnType % "converter")
|
||||
if t.len != 2: localError(c.config, n.info, "a converter takes exactly one argument")
|
||||
addConverterDef(c, s)
|
||||
addConverterDef(c, LazySym(sym: s))
|
||||
|
||||
proc semMacroDef(c: PContext, n: PNode): PNode =
|
||||
result = semProcAux(c, n, skMacro, macroPragmas)
|
||||
|
||||
@@ -925,4 +925,4 @@ proc semPattern(c: PContext, n: PNode; s: PSym): PNode =
|
||||
elif result.len == 0:
|
||||
localError(c.config, n.info, "a pattern cannot be empty")
|
||||
closeScope(c)
|
||||
addPattern(c, s)
|
||||
addPattern(c, LazySym(sym: s))
|
||||
|
||||
@@ -210,7 +210,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
|
||||
)
|
||||
|
||||
if isPure and sfExported in result.sym.flags:
|
||||
addPureEnum(c, result.sym)
|
||||
addPureEnum(c, LazySym(sym: result.sym))
|
||||
if tfNotNil in e.typ.flags and not hasNull:
|
||||
result.incl tfRequiresInit
|
||||
setToStringProc(c.graph, result, genEnumToStrProc(result, n.info, c.graph, c.idgen))
|
||||
@@ -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
|
||||
|
||||
@@ -2219,8 +2219,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
else:
|
||||
result = semTypeNode(c, whenResult, prev)
|
||||
of nkBracketExpr:
|
||||
# Actually len >= 2 is required, but it doesn't print errors nicely with empty brackets
|
||||
checkMinSonsLen(n, 1, c.config)
|
||||
checkMinSonsLen(n, 2, c.config)
|
||||
var head = n[0]
|
||||
var s = if head.kind notin nkCallKinds: semTypeIdent(c, head)
|
||||
else: symFromExpectedTypeNode(c, semExpr(c, head))
|
||||
@@ -2238,21 +2237,10 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
incl result, tfHasAsgn
|
||||
of mVarargs: result = semVarargs(c, n, prev)
|
||||
of mTypeDesc, mType, mTypeOf:
|
||||
if n.len != 2:
|
||||
let name = case s.magic:
|
||||
of mTypeDesc: "typedesc"
|
||||
of mType: "type"
|
||||
of mTypeOf: "typeof"
|
||||
else: ""
|
||||
localError(c.config, n.info, errXExpectsOneTypeParam % name)
|
||||
else:
|
||||
result = makeTypeDesc(c, semTypeNode(c, n[1], nil))
|
||||
result.incl tfExplicit
|
||||
result = makeTypeDesc(c, semTypeNode(c, n[1], nil))
|
||||
result.incl tfExplicit
|
||||
of mStatic:
|
||||
if n.len != 2:
|
||||
localError(c.config, n.info, errXExpectsOneTypeParam % "static")
|
||||
else:
|
||||
result = semStaticType(c, n[1], prev)
|
||||
result = semStaticType(c, n[1], prev)
|
||||
of mExpr:
|
||||
result = semTypeNode(c, n[0], nil)
|
||||
if result != nil:
|
||||
@@ -2262,11 +2250,9 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
for i in 1..<n.len:
|
||||
result.rawAddSon(semTypeNode(c, n[i], nil))
|
||||
of mDistinct:
|
||||
checkSonsLen(n, 2, c.config)
|
||||
result = newOrPrevType(tyDistinct, prev, c)
|
||||
addSonSkipIntLit(result, semTypeNode(c, n[1], nil), c.idgen)
|
||||
of mVar:
|
||||
checkSonsLen(n, 2, c.config)
|
||||
result = newOrPrevType(tyVar, prev, c)
|
||||
var base = semTypeNode(c, n[1], nil)
|
||||
if base.kind in {tyVar, tyLent}:
|
||||
@@ -2390,7 +2376,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
|
||||
|
||||
@@ -373,7 +373,6 @@ proc replaceTypeVarsS(cl: var TReplTypeVars, s: PSym, t: PType): PSym =
|
||||
var g: G[string]
|
||||
|
||||
]#
|
||||
# XXX FIXME This causes system.Natural to be duplicated during compilation of system.nim as cl.owner == nil!
|
||||
result = copySym(s, cl.c.idgen)
|
||||
incl(result.flagsImpl, sfFromGeneric)
|
||||
#idTablePut(cl.symMap, s, result)
|
||||
|
||||
@@ -154,7 +154,7 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
|
||||
assert inst.kind == tyGenericInst
|
||||
c.hashType inst.genericHead, flags, conf
|
||||
for _, a in inst.genericInstParams:
|
||||
c.hashType a, flags+{CoDistinct}, conf
|
||||
c.hashType a, flags, conf
|
||||
t.typeInstImpl = inst
|
||||
return
|
||||
c &= char(t.kind)
|
||||
|
||||
@@ -615,8 +615,6 @@ proc isGenericObjectOf(f, a: PType): bool =
|
||||
# use sym equality to check if the `tyGenericBody` types are equal
|
||||
result = aRoot != nil and f.sym == aRoot.sym
|
||||
|
||||
|
||||
|
||||
proc isObjectSubtype(c: var TCandidate; a, f, fGenericOrigin: PType): int =
|
||||
var t = a
|
||||
assert t.kind == tyObject
|
||||
@@ -1678,6 +1676,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
|
||||
|
||||
@@ -1689,7 +1688,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)
|
||||
@@ -1700,36 +1699,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)
|
||||
@@ -3094,7 +3092,6 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) =
|
||||
put(m, formal.typ, defaultValue.typ)
|
||||
defaultValue.flags.incl nfDefaultParam
|
||||
setSon(m.call, formal.position + 1, defaultValue)
|
||||
|
||||
# forget all inferred types if the overload matching failed
|
||||
if m.state == csNoMatch:
|
||||
for t in m.inferredTypes:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -18,9 +18,21 @@ import std/[intsets, strutils]
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/[assertions, formatfloat]
|
||||
|
||||
export isResolvedUserTypeClass, TPreferedDesc, typeToString
|
||||
|
||||
type
|
||||
TPreferedDesc* = enum
|
||||
preferName, # default
|
||||
preferDesc, # probably should become what preferResolved is
|
||||
preferExported,
|
||||
preferModuleInfo, # fully qualified
|
||||
preferGenericArg,
|
||||
preferTypeName,
|
||||
preferResolved, # fully resolved symbols
|
||||
preferMixed,
|
||||
# most useful, shows: symbol + resolved symbols if it differs, e.g.:
|
||||
# tuple[a: MyInt{int}, b: float]
|
||||
preferInlayHint,
|
||||
preferInferredEffects,
|
||||
|
||||
TTypeRelation* = enum # order is important!
|
||||
isNone, isConvertible,
|
||||
isIntConv,
|
||||
@@ -43,6 +55,8 @@ type
|
||||
pcmNotIterator
|
||||
pcmDifferentCallConv
|
||||
|
||||
proc typeToString*(typ: PType; prefer: TPreferedDesc = preferName): string
|
||||
|
||||
proc addTypeDeclVerboseMaybe*(result: var string, conf: ConfigRef; typ: PType) =
|
||||
if optDeclaredLocs in conf.globalOptions:
|
||||
result.add typeToString(typ, preferMixed)
|
||||
@@ -50,6 +64,8 @@ proc addTypeDeclVerboseMaybe*(result: var string, conf: ConfigRef; typ: PType) =
|
||||
else:
|
||||
result.add typeToString(typ)
|
||||
|
||||
template `$`*(typ: PType): string = typeToString(typ)
|
||||
|
||||
# ------------------- type iterator: ----------------------------------------
|
||||
type
|
||||
TTypeIter* = proc (t: PType, closure: RootRef): bool {.nimcall.} # true if iteration should stop
|
||||
@@ -141,9 +157,15 @@ proc getFloatValue*(n: PNode): BiggestFloat =
|
||||
of nkHiddenStdConv: getFloatValue(n[1])
|
||||
else: NaN
|
||||
|
||||
proc isIntLit*(t: PType): bool {.inline.} =
|
||||
result = t.kind == tyInt and t.n != nil and t.n.kind == nkIntLit
|
||||
|
||||
proc isFloatLit*(t: PType): bool {.inline.} =
|
||||
result = t.kind == tyFloat and t.n != nil and t.n.kind == nkFloatLit
|
||||
|
||||
proc addTypeHeader*(result: var string, conf: ConfigRef; typ: PType; prefer: TPreferedDesc = preferMixed; getDeclarationPath = true) =
|
||||
result.add typeToString(typ, prefer)
|
||||
if getDeclarationPath and typ.sym != nil: result.addDeclaredLoc(conf, typ.sym)
|
||||
if getDeclarationPath: result.addDeclaredLoc(conf, typ.sym)
|
||||
|
||||
proc getProcHeader*(conf: ConfigRef; sym: PSym; prefer: TPreferedDesc = preferName; getDeclarationPath = true): string =
|
||||
assert sym != nil
|
||||
@@ -438,10 +460,337 @@ proc canFormAcycle*(g: ModuleGraph, typ: PType): bool =
|
||||
let t = skipTypes(typ, abstractInst+{tyOwned}-{tyTypeDesc})
|
||||
result = canFormAcycleAux(g, marker, t, t, false, false)
|
||||
|
||||
proc valueToString(a: PNode): string =
|
||||
case a.kind
|
||||
of nkCharLit, nkUIntLit..nkUInt64Lit:
|
||||
result = $cast[uint64](a.intVal)
|
||||
of nkIntLit..nkInt64Lit:
|
||||
result = $a.intVal
|
||||
of nkFloatLit..nkFloat128Lit: result = $a.floatVal
|
||||
of nkStrLit..nkTripleStrLit: result = a.strVal
|
||||
of nkStaticExpr: result = "static(" & a[0].renderTree & ")"
|
||||
else: result = "<invalid value>"
|
||||
|
||||
proc rangeToStr(n: PNode): string =
|
||||
assert(n.kind == nkRange)
|
||||
result = valueToString(n[0]) & ".." & valueToString(n[1])
|
||||
|
||||
const
|
||||
typeToStr: array[TTypeKind, string] = ["None", "bool", "char", "empty",
|
||||
"Alias", "typeof(nil)", "untyped", "typed", "typeDesc",
|
||||
# xxx typeDesc=>typedesc: typedesc is declared as such, and is 10x more common.
|
||||
"GenericInvocation", "GenericBody", "GenericInst", "GenericParam",
|
||||
"distinct $1", "enum", "ordinal[$1]", "array[$1, $2]", "object", "tuple",
|
||||
"set[$1]", "range[$1]", "ptr ", "ref ", "var ", "seq[$1]", "proc",
|
||||
"pointer", "OpenArray[$1]", "string", "cstring", "Forward",
|
||||
"int", "int8", "int16", "int32", "int64",
|
||||
"float", "float32", "float64", "float128",
|
||||
"uint", "uint8", "uint16", "uint32", "uint64",
|
||||
"owned", "sink",
|
||||
"lent ", "varargs[$1]", "UncheckedArray[$1]", "Error Type",
|
||||
"BuiltInTypeClass", "UserTypeClass",
|
||||
"UserTypeClassInst", "CompositeTypeClass", "inferred",
|
||||
"and", "or", "not", "any", "static", "TypeFromExpr", "concept", # xxx bugfix
|
||||
"void", "iterable"]
|
||||
|
||||
const preferToResolveSymbols = {preferName, preferTypeName, preferModuleInfo,
|
||||
preferGenericArg, preferResolved, preferMixed, preferInlayHint, preferInferredEffects}
|
||||
|
||||
template bindConcreteTypeToUserTypeClass*(tc, concrete: PType) =
|
||||
tc.add concrete
|
||||
tc.incl tfResolved
|
||||
|
||||
# TODO: It would be a good idea to kill the special state of a resolved
|
||||
# concept by switching to tyAlias within the instantiated procs.
|
||||
# Currently, tyAlias is always skipped with skipModifier, which means that
|
||||
# we can store information about the matched concept in another position.
|
||||
# Then builtInFieldAccess can be modified to properly read the derived
|
||||
# consts and types stored within the concept.
|
||||
template isResolvedUserTypeClass*(t: PType): bool =
|
||||
tfResolved in t.flags
|
||||
|
||||
proc addTypeFlags(name: var string, typ: PType) {.inline.} =
|
||||
if tfNotNil in typ.flags: name.add(" not nil")
|
||||
|
||||
proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
|
||||
let preferToplevel = prefer
|
||||
proc getPrefer(prefer: TPreferedDesc): TPreferedDesc =
|
||||
if preferToplevel in {preferResolved, preferMixed}:
|
||||
preferToplevel # sticky option
|
||||
else:
|
||||
prefer
|
||||
|
||||
proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
|
||||
result = ""
|
||||
let prefer = getPrefer(prefer)
|
||||
let t = typ
|
||||
if t == nil: return
|
||||
if prefer in preferToResolveSymbols and t.sym != nil and
|
||||
sfAnon notin t.sym.flags and t.kind notin {tySequence, tyInferred}:
|
||||
if t.kind == tyInt and isIntLit(t):
|
||||
if prefer == preferInlayHint:
|
||||
result = t.sym.name.s
|
||||
else:
|
||||
result = t.sym.name.s & " literal(" & $t.n.intVal & ")"
|
||||
elif t.kind == tyAlias and t.elementType.kind != tyAlias:
|
||||
result = typeToString(t.elementType)
|
||||
elif prefer in {preferResolved, preferMixed}:
|
||||
case t.kind
|
||||
of IntegralTypes + {tyFloat..tyFloat128} + {tyString, tyCstring}:
|
||||
result = typeToStr[t.kind]
|
||||
of tyGenericBody:
|
||||
result = typeToString(t.last)
|
||||
of tyCompositeTypeClass:
|
||||
# avoids showing `A[any]` in `proc fun(a: A)` with `A = object[T]`
|
||||
result = typeToString(t.last.last)
|
||||
else:
|
||||
result = t.sym.name.s
|
||||
if prefer == preferMixed and result != t.sym.name.s:
|
||||
result = t.sym.name.s & "{" & result & "}"
|
||||
elif prefer in {preferName, preferTypeName, preferInlayHint, preferInferredEffects} or t.sym.owner.isNil:
|
||||
# note: should probably be: {preferName, preferTypeName, preferGenericArg}
|
||||
result = t.sym.name.s
|
||||
if t.kind == tyGenericParam and t.genericParamHasConstraints:
|
||||
result.add ": "
|
||||
result.add t.elementType.typeToString
|
||||
else:
|
||||
result = t.sym.owner.name.s & '.' & t.sym.name.s
|
||||
result.addTypeFlags(t)
|
||||
return
|
||||
case t.kind
|
||||
of tyInt:
|
||||
if not isIntLit(t) or prefer == preferExported:
|
||||
result = typeToStr[t.kind]
|
||||
else:
|
||||
case prefer:
|
||||
of preferGenericArg:
|
||||
result = $t.n.intVal
|
||||
of preferInlayHint:
|
||||
result = "int"
|
||||
else:
|
||||
result = "int literal(" & $t.n.intVal & ")"
|
||||
of tyGenericInst:
|
||||
result = typeToString(t.genericHead) & '['
|
||||
for needsComma, a in t.genericInstParams:
|
||||
if needsComma: result.add(", ")
|
||||
result.add(typeToString(a, preferGenericArg))
|
||||
result.add(']')
|
||||
of tyGenericInvocation:
|
||||
result = typeToString(t.genericHead) & '['
|
||||
for needsComma, a in t.genericInvocationParams:
|
||||
if needsComma: result.add(", ")
|
||||
result.add(typeToString(a, preferGenericArg))
|
||||
result.add(']')
|
||||
of tyGenericBody:
|
||||
result = typeToString(t.typeBodyImpl) & '['
|
||||
for i, a in t.genericBodyParams:
|
||||
if i > 0: result.add(", ")
|
||||
result.add(typeToString(a, preferTypeName))
|
||||
result.add(']')
|
||||
of tyTypeDesc:
|
||||
if t.elementType.kind == tyNone: result = "typedesc"
|
||||
else: result = "typedesc[" & typeToString(t.elementType) & "]"
|
||||
of tyStatic:
|
||||
if prefer == preferGenericArg and t.n != nil:
|
||||
result = t.n.renderTree
|
||||
else:
|
||||
result = "static[" & (if t.hasElementType: typeToString(t.skipModifier) else: "") & "]"
|
||||
if t.n != nil: result.add "(" & renderTree(t.n) & ")"
|
||||
of tyUserTypeClass:
|
||||
if t.sym != nil and t.sym.owner != nil:
|
||||
if t.isResolvedUserTypeClass: return typeToString(t.last)
|
||||
return t.sym.owner.name.s
|
||||
else:
|
||||
result = "<invalid tyUserTypeClass>"
|
||||
of tyBuiltInTypeClass:
|
||||
result =
|
||||
case t.base.kind
|
||||
of tyVar: "var"
|
||||
of tyRef: "ref"
|
||||
of tyPtr: "ptr"
|
||||
of tySequence: "seq"
|
||||
of tyArray: "array"
|
||||
of tySet: "set"
|
||||
of tyRange: "range"
|
||||
of tyDistinct: "distinct"
|
||||
of tyProc: "proc"
|
||||
of tyObject: "object"
|
||||
of tyTuple: "tuple"
|
||||
of tyOpenArray: "openArray"
|
||||
else: typeToStr[t.base.kind]
|
||||
of tyInferred:
|
||||
let concrete = t.previouslyInferred
|
||||
if concrete != nil: result = typeToString(concrete)
|
||||
else: result = "inferred[" & typeToString(t.base) & "]"
|
||||
of tyUserTypeClassInst:
|
||||
let body = t.base
|
||||
result = body.sym.name.s & "["
|
||||
for needsComma, a in t.userTypeClassInstParams:
|
||||
if needsComma: result.add(", ")
|
||||
result.add(typeToString(a))
|
||||
result.add "]"
|
||||
of tyAnd:
|
||||
for i, son in t.ikids:
|
||||
if i > 0: result.add(" and ")
|
||||
result.add(typeToString(son))
|
||||
of tyOr:
|
||||
for i, son in t.ikids:
|
||||
if i > 0: result.add(" or ")
|
||||
result.add(typeToString(son))
|
||||
of tyNot:
|
||||
result = "not " & typeToString(t.elementType)
|
||||
of tyUntyped:
|
||||
#internalAssert t.len == 0
|
||||
result = "untyped"
|
||||
of tyFromExpr:
|
||||
if t.n == nil:
|
||||
result = "unknown"
|
||||
else:
|
||||
result = "typeof(" & renderTree(t.n) & ")"
|
||||
of tyArray:
|
||||
result = "array"
|
||||
if t.hasElementType:
|
||||
if t.indexType.kind == tyRange:
|
||||
result &= "[" & rangeToStr(t.indexType.n) & ", " &
|
||||
typeToString(t.elementType) & ']'
|
||||
else:
|
||||
result &= "[" & typeToString(t.indexType) & ", " &
|
||||
typeToString(t.elementType) & ']'
|
||||
of tyUncheckedArray:
|
||||
result = "UncheckedArray"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.elementType) & ']'
|
||||
of tySequence:
|
||||
if t.sym != nil and prefer != preferResolved:
|
||||
result = t.sym.name.s
|
||||
else:
|
||||
result = "seq"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.elementType) & ']'
|
||||
of tyOrdinal:
|
||||
result = "ordinal"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.skipModifier) & ']'
|
||||
of tySet:
|
||||
result = "set"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.elementType) & ']'
|
||||
of tyOpenArray:
|
||||
result = "openArray"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.elementType) & ']'
|
||||
of tyDistinct:
|
||||
result = "distinct " & typeToString(t.elementType,
|
||||
if prefer == preferModuleInfo: preferModuleInfo else: preferTypeName)
|
||||
of tyIterable:
|
||||
# xxx factor this pattern
|
||||
result = "iterable"
|
||||
if t.hasElementType:
|
||||
result &= "[" & typeToString(t.skipModifier) & ']'
|
||||
of tyTuple:
|
||||
# we iterate over t.sons here, because t.n may be nil
|
||||
if t.n != nil:
|
||||
result = "tuple["
|
||||
for i in 0..<t.n.len:
|
||||
assert(t.n[i].kind == nkSym)
|
||||
result.add(t.n[i].sym.name.s & ": " & typeToString(t.n[i].sym.typ))
|
||||
if i < t.n.len - 1: result.add(", ")
|
||||
result.add(']')
|
||||
elif t.isEmptyTupleType:
|
||||
result = "tuple[]"
|
||||
elif t.isSingletonTupleType:
|
||||
result = "("
|
||||
for son in t.kids:
|
||||
result.add(typeToString(son))
|
||||
result.add(",)")
|
||||
else:
|
||||
result = "("
|
||||
for i, son in t.ikids:
|
||||
if i > 0: result.add ", "
|
||||
result.add(typeToString(son))
|
||||
result.add(')')
|
||||
of tyPtr, tyRef, tyVar, tyLent:
|
||||
result = if isOutParam(t): "out " else: typeToStr[t.kind]
|
||||
result.add typeToString(t.elementType)
|
||||
of tyRange:
|
||||
result = "range "
|
||||
if t.n != nil and t.n.kind == nkRange:
|
||||
result.add rangeToStr(t.n)
|
||||
if prefer != preferExported:
|
||||
result.add("(" & typeToString(t.elementType) & ")")
|
||||
of tyProc:
|
||||
result = if tfIterator in t.flags: "iterator "
|
||||
elif t.owner != nil:
|
||||
case t.owner.kind
|
||||
of skTemplate: "template "
|
||||
of skMacro: "macro "
|
||||
of skConverter: "converter "
|
||||
else: "proc "
|
||||
else:
|
||||
"proc "
|
||||
if tfUnresolved in t.flags: result.add "[*missing parameters*]"
|
||||
result.add "("
|
||||
for i, a in t.paramTypes:
|
||||
if i > FirstParamAt: result.add(", ")
|
||||
let j = paramTypeToNodeIndex(i)
|
||||
if t.n != nil and j < t.n.len and t.n[j].kind == nkSym:
|
||||
result.add(t.n[j].sym.name.s)
|
||||
result.add(": ")
|
||||
result.add(typeToString(a))
|
||||
result.add(')')
|
||||
if t.returnType != nil: result.add(": " & typeToString(t.returnType))
|
||||
var prag = if t.callConv == ccNimCall and tfExplicitCallConv notin t.flags: "" else: $t.callConv
|
||||
var hasImplicitRaises = false
|
||||
if not isNil(t.owner) and not isNil(t.owner.ast) and (t.owner.ast.len - 1) >= pragmasPos:
|
||||
let pragmasNode = t.owner.ast[pragmasPos]
|
||||
let raisesSpec = effectSpec(pragmasNode, wRaises)
|
||||
if not isNil(raisesSpec):
|
||||
addSep(prag)
|
||||
prag.add("raises: ")
|
||||
prag.add($raisesSpec)
|
||||
hasImplicitRaises = true
|
||||
if tfNoSideEffect in t.flags:
|
||||
addSep(prag)
|
||||
prag.add("noSideEffect")
|
||||
if tfThread in t.flags:
|
||||
addSep(prag)
|
||||
prag.add("gcsafe")
|
||||
var effectsOfStr = ""
|
||||
for i, a in t.paramTypes:
|
||||
let j = paramTypeToNodeIndex(i)
|
||||
if t.n != nil and j < t.n.len and t.n[j].kind == nkSym and t.n[j].sym.kind == skParam and sfEffectsDelayed in t.n[j].sym.flags:
|
||||
addSep(effectsOfStr)
|
||||
effectsOfStr.add(t.n[j].sym.name.s)
|
||||
if effectsOfStr != "":
|
||||
addSep(prag)
|
||||
prag.add("effectsOf: ")
|
||||
prag.add(effectsOfStr)
|
||||
if not hasImplicitRaises and prefer == preferInferredEffects and not isNil(t.owner) and not isNil(t.owner.typ) and not isNil(t.owner.typ.n) and (t.owner.typ.n.len > 0):
|
||||
let effects = t.n[0]
|
||||
if effects.kind == nkEffectList and effects.len == effectListLen:
|
||||
var inferredRaisesStr = ""
|
||||
let effs = effects[exceptionEffects]
|
||||
if not isNil(effs):
|
||||
for eff in items(effs):
|
||||
if not isNil(eff):
|
||||
addSep(inferredRaisesStr)
|
||||
inferredRaisesStr.add($eff.typ)
|
||||
addSep(prag)
|
||||
prag.add("raises: <inferred> [")
|
||||
prag.add(inferredRaisesStr)
|
||||
prag.add("]")
|
||||
if prag.len != 0: result.add("{." & prag & ".}")
|
||||
of tyVarargs:
|
||||
result = typeToStr[t.kind] % typeToString(t.elementType)
|
||||
of tySink:
|
||||
result = "sink " & typeToString(t.skipModifier)
|
||||
of tyOwned:
|
||||
result = "owned " & typeToString(t.elementType)
|
||||
else:
|
||||
result = typeToStr[t.kind]
|
||||
result.addTypeFlags(t)
|
||||
result = typeToString(typ, prefer)
|
||||
|
||||
proc firstOrd*(conf: ConfigRef; t: PType): Int128 =
|
||||
case t.kind
|
||||
of tyBool, tyChar, tySequence, tyOpenArray, tyString, tyVarargs, tyError:
|
||||
|
||||
@@ -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:
|
||||
@@ -663,10 +663,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||
of rkNode:
|
||||
if regs[rb].node.typ.kind notin PtrLikeKinds:
|
||||
stackTrace(c, tos, pc, "opcCastIntToPtr: regs[rb].node.typ: " & $regs[rb].node.typ.kind)
|
||||
if regs[rb].node.kind == nkNilLit:
|
||||
node2.intVal = 0
|
||||
else:
|
||||
node2.intVal = regs[rb].node.intVal
|
||||
node2.intVal = regs[rb].node.intVal
|
||||
else: stackTrace(c, tos, pc, "opcCastIntToPtr: regs[rb].kind: " & $regs[rb].kind)
|
||||
regs[ra].node = node2
|
||||
of opcAsgnComplex:
|
||||
@@ -1840,20 +1837,6 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||
case a.kind
|
||||
of nkFloatLit..nkFloat64Lit: regs[ra].floatVal = a.floatVal
|
||||
else: stackTrace(c, tos, pc, errFieldXNotFound & "floatVal")
|
||||
of opcNSymbol:
|
||||
decodeB(rkNode)
|
||||
let a = regs[rb].node
|
||||
if a.kind == nkSym:
|
||||
regs[ra].node = copyNode(a)
|
||||
else:
|
||||
stackTrace(c, tos, pc, errFieldXNotFound & "symbol")
|
||||
of opcNIdent:
|
||||
decodeB(rkNode)
|
||||
let a = regs[rb].node
|
||||
if a.kind == nkIdent:
|
||||
regs[ra].node = copyNode(a)
|
||||
else:
|
||||
stackTrace(c, tos, pc, errFieldXNotFound & "ident")
|
||||
of opcNodeId:
|
||||
decodeB(rkInt)
|
||||
when defined(useNodeIds):
|
||||
@@ -2159,20 +2142,6 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||
dest.floatVal = regs[rb].floatVal
|
||||
else:
|
||||
stackTrace(c, tos, pc, errFieldXNotFound & "floatVal")
|
||||
of opcNSetSymbol:
|
||||
decodeB(rkNode)
|
||||
var dest = regs[ra].node
|
||||
if dest.kind == nkSym and regs[rb].node.kind == nkSym:
|
||||
dest.sym = regs[rb].node.sym
|
||||
else:
|
||||
stackTrace(c, tos, pc, errFieldXNotFound & "symbol")
|
||||
of opcNSetIdent:
|
||||
decodeB(rkNode)
|
||||
var dest = regs[ra].node
|
||||
if dest.kind == nkIdent and regs[rb].node.kind == nkIdent:
|
||||
dest.ident = regs[rb].node.ident
|
||||
else:
|
||||
stackTrace(c, tos, pc, errFieldXNotFound & "ident")
|
||||
of opcNSetStrVal:
|
||||
decodeB(rkNode)
|
||||
var dest = regs[ra].node
|
||||
|
||||
@@ -119,15 +119,13 @@ type
|
||||
opcNSymKind,
|
||||
opcNIntVal,
|
||||
opcNFloatVal,
|
||||
opcNSymbol,
|
||||
opcNIdent,
|
||||
opcNGetType,
|
||||
opcNStrVal,
|
||||
opcNSigHash,
|
||||
opcNGetSize,
|
||||
|
||||
opcNSetIntVal,
|
||||
opcNSetFloatVal, opcNSetSymbol, opcNSetIdent, opcNSetStrVal,
|
||||
opcNSetFloatVal, opcNSetStrVal,
|
||||
opcNNewNimNode, opcNCopyNimNode, opcNCopyNimTree, opcNDel, opcGenSym,
|
||||
|
||||
opcNccValue, opcNccInc, opcNcsAdd, opcNcsIncl, opcNcsLen, opcNcsAt,
|
||||
|
||||
@@ -803,8 +803,6 @@ proc genNarrow(c: PCtx; n: PNode; dest: TDest) =
|
||||
let first = c.genx(newIntTypeNode(firstOrd(c.config, t), intType))
|
||||
let last = c.genx(newIntTypeNode(lastOrd(c.config, t), intType))
|
||||
c.gABC(n, opcNarrowR, dest, first, last)
|
||||
c.freeTemp(first)
|
||||
c.freeTemp(last)
|
||||
|
||||
proc genNarrowU(c: PCtx; n: PNode; dest: TDest) =
|
||||
let t = skipTypes(n.typ, abstractVar-{tyTypeDesc})
|
||||
@@ -1375,8 +1373,6 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}, m: TMag
|
||||
|
||||
of mNIntVal: genUnaryABC(c, n, dest, opcNIntVal)
|
||||
of mNFloatVal: genUnaryABC(c, n, dest, opcNFloatVal)
|
||||
of mNSymbol: genUnaryABC(c, n, dest, opcNSymbol)
|
||||
of mNIdent: genUnaryABC(c, n, dest, opcNIdent)
|
||||
of mNGetType:
|
||||
let tmp = c.genx(n[1])
|
||||
if dest < 0: dest = c.getTemp(n.typ)
|
||||
@@ -1403,12 +1399,6 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}, m: TMag
|
||||
of mNSetFloatVal:
|
||||
unused(c, n, dest)
|
||||
genBinaryStmt(c, n, opcNSetFloatVal)
|
||||
of mNSetSymbol:
|
||||
unused(c, n, dest)
|
||||
genBinaryStmt(c, n, opcNSetSymbol)
|
||||
of mNSetIdent:
|
||||
unused(c, n, dest)
|
||||
genBinaryStmt(c, n, opcNSetIdent)
|
||||
of mNSetStrVal:
|
||||
unused(c, n, dest)
|
||||
genBinaryStmt(c, n, opcNSetStrVal)
|
||||
@@ -1588,6 +1578,7 @@ proc genAsgn(c: PCtx; dest: TDest; ri: PNode; requiresCopy: bool) =
|
||||
proc setSlot(c: PCtx; v: PSym) =
|
||||
# XXX generate type initialization here?
|
||||
if v.position == 0:
|
||||
# IC: review this solution again later
|
||||
v.positionImpl = getFreeRegister(c, if v.kind == skLet: slotFixedLet else: slotFixedVar, start = 1)
|
||||
|
||||
template cannotEval(c: PCtx; n: PNode) =
|
||||
|
||||
@@ -82,7 +82,7 @@ proc containGenerics(base: PType, s: seq[tuple[depth: int, value: PType]]): bool
|
||||
break
|
||||
|
||||
proc collectVTableDispatchers*(g: ModuleGraph) =
|
||||
var itemTable = initTable[ItemId, seq[PSym]]()
|
||||
var itemTable = initTable[ItemId, seq[LazySym]]()
|
||||
var rootTypeSeq = newSeq[PType]()
|
||||
var rootItemIdCount = initCountTable[ItemId]()
|
||||
for bucket in 0..<g.methods.len:
|
||||
@@ -95,7 +95,7 @@ proc collectVTableDispatchers*(g: ModuleGraph) =
|
||||
let methodIndexLen = g.bucketTable[baseType.itemId]
|
||||
if baseType.itemId notin itemTable: # once is enough
|
||||
rootTypeSeq.add baseType
|
||||
itemTable[baseType.itemId] = newSeq[PSym](methodIndexLen)
|
||||
itemTable[baseType.itemId] = newSeq[LazySym](methodIndexLen)
|
||||
|
||||
sort(g.objectTree[baseType.itemId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
|
||||
if x.depth >= y.depth: 1
|
||||
@@ -104,7 +104,7 @@ proc collectVTableDispatchers*(g: ModuleGraph) =
|
||||
|
||||
for item in g.objectTree[baseType.itemId]:
|
||||
if item.value.itemId notin itemTable:
|
||||
itemTable[item.value.itemId] = newSeq[PSym](methodIndexLen)
|
||||
itemTable[item.value.itemId] = newSeq[LazySym](methodIndexLen)
|
||||
|
||||
var mIndex = 0 # here is the correpsonding index
|
||||
if baseType.itemId notin rootItemIdCount:
|
||||
@@ -114,13 +114,13 @@ proc collectVTableDispatchers*(g: ModuleGraph) =
|
||||
rootItemIdCount.inc(baseType.itemId)
|
||||
for idx in 0..<g.methods[bucket].methods.len:
|
||||
let obj = g.methods[bucket].methods[idx].typ.firstParamType.skipTypes(skipPtrs)
|
||||
itemTable[obj.itemId][mIndex] = g.methods[bucket].methods[idx]
|
||||
itemTable[obj.itemId][mIndex] = LazySym(sym: g.methods[bucket].methods[idx])
|
||||
g.addDispatchers genVTableDispatcher(g, g.methods[bucket].methods, mIndex)
|
||||
else: # if the base object doesn't have this method
|
||||
g.addDispatchers genIfDispatcher(g, g.methods[bucket].methods, relevantCols, g.idgen)
|
||||
|
||||
proc sortVTableDispatchers*(g: ModuleGraph) =
|
||||
var itemTable = initTable[ItemId, seq[PSym]]()
|
||||
var itemTable = initTable[ItemId, seq[LazySym]]()
|
||||
var rootTypeSeq = newSeq[ItemId]()
|
||||
var rootItemIdCount = initCountTable[ItemId]()
|
||||
for bucket in 0..<g.methods.len:
|
||||
@@ -133,7 +133,7 @@ proc sortVTableDispatchers*(g: ModuleGraph) =
|
||||
let methodIndexLen = g.bucketTable[baseType.itemId]
|
||||
if baseType.itemId notin itemTable: # once is enough
|
||||
rootTypeSeq.add baseType.itemId
|
||||
itemTable[baseType.itemId] = newSeq[PSym](methodIndexLen)
|
||||
itemTable[baseType.itemId] = newSeq[LazySym](methodIndexLen)
|
||||
|
||||
sort(g.objectTree[baseType.itemId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
|
||||
if x.depth >= y.depth: 1
|
||||
@@ -142,7 +142,7 @@ proc sortVTableDispatchers*(g: ModuleGraph) =
|
||||
|
||||
for item in g.objectTree[baseType.itemId]:
|
||||
if item.value.itemId notin itemTable:
|
||||
itemTable[item.value.itemId] = newSeq[PSym](methodIndexLen)
|
||||
itemTable[item.value.itemId] = newSeq[LazySym](methodIndexLen)
|
||||
|
||||
var mIndex = 0 # here is the correpsonding index
|
||||
if baseType.itemId notin rootItemIdCount:
|
||||
@@ -152,7 +152,7 @@ proc sortVTableDispatchers*(g: ModuleGraph) =
|
||||
rootItemIdCount.inc(baseType.itemId)
|
||||
for idx in 0..<g.methods[bucket].methods.len:
|
||||
let obj = g.methods[bucket].methods[idx].typ.firstParamType.skipTypes(skipPtrs)
|
||||
itemTable[obj.itemId][mIndex] = g.methods[bucket].methods[idx]
|
||||
itemTable[obj.itemId][mIndex] = LazySym(sym: g.methods[bucket].methods[idx])
|
||||
|
||||
for baseType in rootTypeSeq:
|
||||
g.setMethodsPerType(baseType, itemTable[baseType])
|
||||
@@ -160,7 +160,7 @@ proc sortVTableDispatchers*(g: ModuleGraph) =
|
||||
let typ = item.value.skipTypes(skipPtrs)
|
||||
let idx = typ.itemId
|
||||
for mIndex in 0..<itemTable[idx].len:
|
||||
if itemTable[idx][mIndex] == nil:
|
||||
if itemTable[idx][mIndex].sym == nil:
|
||||
let parentIndex = typ.baseClass.skipTypes(skipPtrs).itemId
|
||||
itemTable[idx][mIndex] = itemTable[parentIndex][mIndex]
|
||||
g.setMethodsPerType(idx, itemTable[idx])
|
||||
|
||||
@@ -115,7 +115,6 @@ Advanced options:
|
||||
--docSeeSrcUrl:url activate 'see source' for doc command
|
||||
(see doc.item.seesrc in config/nimdoc.cfg)
|
||||
--docInternal also generate documentation for non-exported symbols
|
||||
--raw turn off markup rendering for JSON docs
|
||||
--lineDir:on|off generation of #line directive on|off
|
||||
--embedsrc:on|off embeds the original source code as comments
|
||||
in the generated output
|
||||
|
||||
@@ -761,7 +761,7 @@ used to specialize the object traversal in order to avoid deep recursions:
|
||||
if x.left != nil: s.add(x.left)
|
||||
if x.right != nil: s.add(x.right)
|
||||
# free the memory explicitly:
|
||||
deallocRef(x)
|
||||
`=dispose`(x)
|
||||
# notice how even the destructor for 's' is not called implicitly
|
||||
# anymore thanks to .nodestroy, so we have to call it on our own:
|
||||
`=destroy`(s)
|
||||
|
||||
166
doc/ic.md
166
doc/ic.md
@@ -1,166 +0,0 @@
|
||||
======================================
|
||||
Incremental Compilation (IC)
|
||||
======================================
|
||||
|
||||
The ``nim ic`` command provides incremental compilation support for Nim projects,
|
||||
allowing faster rebuilds by reusing previously compiled intermediate representations
|
||||
of modules that haven't changed.
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
Incremental compilation works by decomposing the compilation process into several stages:
|
||||
|
||||
1. **Parsing** - Source files are parsed into an abstract syntax tree (AST)
|
||||
2. **Semantic Analysis** - Symbols are resolved and type checking is performed
|
||||
3. **Code Generation** - Platform-specific code is generated from the analyzed AST
|
||||
4. **Linking** - The generated code is linked into an executable
|
||||
|
||||
The IC mechanism caches the results of earlier stages in NIF files
|
||||
(Nim intermediate format): ``.p.nif`` (parsed), ``.deps.nif`` (dependencies),
|
||||
and ``.nif`` (semantically analyzed). When recompiling, only modules that have
|
||||
changed need to be reprocessed through the semantic analysis and code generation
|
||||
stages, significantly reducing compilation time for large projects.
|
||||
|
||||
NIF File Format
|
||||
===============
|
||||
|
||||
NIF (Nim Intermediate Format) files are text-based files that use a Lisp-like
|
||||
syntax. They employ a hybrid format where byte offsets into the text are used for
|
||||
efficient access, making them simultaneously human-readable and machine-efficient.
|
||||
The text representation is particularly valuable for debugging and introspection.
|
||||
|
||||
Each ``.nim`` module produces its own ``.nif`` file during compilation.
|
||||
The NIF format contains:
|
||||
|
||||
- **Header** - Version information (e.g., `(.nif26)`)
|
||||
- **Dependencies** - List of source files and dependencies
|
||||
- **Interface** - Exported symbols and their indices
|
||||
- **Body** - The intermediate representation of the module's code in Lisp-like syntax
|
||||
|
||||
The NIF format is designed specifically for Nim and allows efficient serialization
|
||||
and deserialization of the compiler's intermediate representation while remaining
|
||||
readable and debuggable by tools and developers.
|
||||
|
||||
The ``nim ic`` Switch
|
||||
=====================
|
||||
|
||||
The ``nim ic`` command initiates incremental compilation for a project.
|
||||
It automatically manages the build process by:
|
||||
|
||||
1. Parsing all source files into ``.nif`` format (using the ``nifler`` tool)
|
||||
2. Performing semantic analysis on modified modules
|
||||
3. Generating code only for modules with changes or dependencies on changed modules
|
||||
4. Generating a build file (in NIFMake format) that orchestrates the compilation
|
||||
5. Executing the build file through ``nifmake``
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
- **nifler** - Tool for parsing Nim source files into NIF format. The ``nim ic`` command uses ``nifler parse --deps`` to generate both parsed files (``.p.nif``) and dependency files (``.deps.nif``).
|
||||
- **nifmake** - Build orchestration tool that follows dependencies and executes the build rules defined in ``.build.nif`` files.
|
||||
|
||||
If these tools are not available, ``nim ic`` will display instructions on how to
|
||||
obtain them.
|
||||
|
||||
Key Modules for IC Logic
|
||||
=========================
|
||||
|
||||
The primary modules in the compiler that handle incremental compilation logic are:
|
||||
|
||||
- **deps.nim** - Dependency analysis and build file generation. Contains the
|
||||
``commandIc`` procedure which is the main entry point for the ``nim ic`` command.
|
||||
This module orchestrates the incremental compilation process, handling dependency
|
||||
traversal (via ``nifler deps``), build rule generation, and build file creation.
|
||||
The build file is written to ``nifcache/`` directory. This module also explicitly
|
||||
models ``system.nim`` as a dependency of all modules.
|
||||
|
||||
- **ast2nif.nim** - Core mapping between AST and NIF.
|
||||
|
||||
|
||||
**Code, Logic & Debugging**
|
||||
===========================
|
||||
|
||||
This section focuses on the compiler-side code paths, the logic you will
|
||||
inspect while debugging IC, and a pragmatic manual workflow for bug hunting
|
||||
using local invocations such as ``nim m --nimcache:nifcache``.
|
||||
|
||||
Core places to inspect
|
||||
- **`compiler/deps.nim`**: generates the NIF-based build file and implements
|
||||
``commandIc`` (entry point for ``nim ic``). Look for how build rules are
|
||||
emitted (calls to the NIF builder) and how inputs/outputs are wired.
|
||||
- **`compiler/modulegraphs.nim`** and **`compiler/pipelines.nim`**:
|
||||
dependency graph and compilation pipeline integration — useful when a module
|
||||
is rebuilt unexpectedly.
|
||||
|
||||
Understanding the NIF text
|
||||
- NIF files are human-readable; open the per-module ``.nif`` files in
|
||||
``nifcache/`` to inspect parsed ASTs, dependency lists and interface tables.
|
||||
- Because NIF uses textual nodes and byte offsets, tools can quickly seek to
|
||||
positions in the file — but for debugging you usually only need to read the
|
||||
file top-to-bottom.
|
||||
|
||||
Manual bug-hunting workflow
|
||||
- Prepare a clean nimcache directory (relative to your project):
|
||||
|
||||
```bash
|
||||
mkdir -p nifcache
|
||||
```
|
||||
|
||||
- Parse/semantic-check a single module and write NIF/sem artifacts:
|
||||
|
||||
```bash
|
||||
nim m --nimcache:nifcache path/to/module.nim
|
||||
```
|
||||
|
||||
- ``nim m`` runs the compiler up to the semantic checking stage for the
|
||||
specified module and emits intermediate cache files into ``nifcache/``.
|
||||
- Use this to reproduce and isolate failures in the semantic stage.
|
||||
|
||||
- Inspect the generated files for that module under ``nifcache/`` (look for
|
||||
``.nif``, sem/parsed artifacts). Because NIF is text-based you can open and
|
||||
grep it directly:
|
||||
|
||||
```bash
|
||||
sed -n '1,200p' nifcache/ModuleName.nif
|
||||
grep -n "someSymbol" -n nifcache/ModuleName.nif
|
||||
```
|
||||
|
||||
- To reproduce a full incremental compilation of the project, generate the
|
||||
build file and run it (``nim ic`` automates this). The build file is generated
|
||||
in ``nifcache/`` directory. To debug an individual build step, run the command
|
||||
that the build file would execute manually:
|
||||
- Parsing step: ``nifler parse --deps input.nim`` (produces ``.p.nif`` and ``.deps.nif``)
|
||||
- Semantic step: ``nim m --nimcache:nifcache input.nim`` (produces ``.nif``)
|
||||
- Code generation: ``nim nifc --nimcache:nifcache input.nim`` (produces executable)
|
||||
|
||||
- Force a cache invalidation for a single module by removing its NIF/sem
|
||||
artifact and re-running the semantic step:
|
||||
|
||||
```bash
|
||||
rm nifcache/ModuleName.nif
|
||||
nim m --nimcache:nifcache path/to/ModuleName.nim
|
||||
```
|
||||
|
||||
- When investigating incorrect replayed state (pragmas, `{.compile: ...}`):
|
||||
inspect the replay actions in ``compiler/ic/replayer.nim`` and open the
|
||||
module's NIF to find the ``toReplay``/action entries that will be executed
|
||||
during reload.
|
||||
|
||||
Tips for efficient debugging
|
||||
- Use ``--path:...`` flags when invoking ``nim m`` to emulate the exact
|
||||
search paths used in your project, e.g. ``--path:lib --path:vendor``.
|
||||
- Compare two successive ``.nif`` files with ``diff`` to see what changed and
|
||||
why a module was rebuilt.
|
||||
|
||||
Where to change behavior
|
||||
- Cache invalidation decisions and build-rule emission are implemented in
|
||||
``compiler/deps.nim``. When investigating surprising
|
||||
rebuilds, instrument those modules to log the footprint/hash/comparison
|
||||
outcome.
|
||||
|
||||
See also
|
||||
========
|
||||
|
||||
- `nif-spec` - NIF format specification (text format and node grammar):
|
||||
[nifspec/doc/nif-spec.md](../nifspec/doc/nif-spec.md)
|
||||
6
koch.nim
6
koch.nim
@@ -16,7 +16,7 @@ const
|
||||
ChecksumsStableCommit = "0b8e46379c5bc1bf73d8b3011908389c60fb9b98" # 2.0.1
|
||||
SatStableCommit = "faf1617f44d7632ee9601ebc13887644925dcc01"
|
||||
|
||||
NimonyStableCommit = "deb9b50c573fb55e071825ab55385e293b7216d5" # unversioned \
|
||||
NimonyStableCommit = "fc8baa61b9911caf4666685a5f5ed41b9c04f6f8" # unversioned \
|
||||
# Note that Nimony uses Nim as a git submodule but we don't want to install
|
||||
# Nimony's dependency to Nim as we are Nim. So a `git clone` without --recursive
|
||||
# is **required** here.
|
||||
@@ -558,7 +558,9 @@ proc icTest(args: string) =
|
||||
for fragment in content.split("#!EDIT!#"):
|
||||
let file = inp.replace(".nim", "_temp.nim")
|
||||
writeFile(file, fragment)
|
||||
var cmd = nimExe & " ic --hint:Conf:off --warnings:off "
|
||||
var cmd = nimExe & " cpp --ic:legacy -d:nimIcIntegrityChecks --listcmd "
|
||||
if i == 0:
|
||||
cmd.add "-f "
|
||||
cmd.add quoteShell(file)
|
||||
exec(cmd)
|
||||
inc i
|
||||
|
||||
@@ -117,7 +117,6 @@ type
|
||||
ntyCompositeTypeClass, ntyInferred, ntyAnd, ntyOr, ntyNot,
|
||||
ntyAnything, ntyStatic, ntyFromExpr, ntyOptDeprecated, ntyVoid
|
||||
|
||||
TNimTypeKinds* {.deprecated.} = set[NimTypeKind]
|
||||
NimSymKind* = enum
|
||||
nskUnknown, nskConditional, nskDynLib, nskParam,
|
||||
nskGenericParam, nskTemp, nskModule, nskType, nskVar, nskLet,
|
||||
@@ -127,24 +126,10 @@ type
|
||||
nskEnumField, nskForVar, nskLabel,
|
||||
nskStub
|
||||
|
||||
TNimSymKinds* {.deprecated.} = set[NimSymKind]
|
||||
|
||||
const
|
||||
nnkMutableTy* {.deprecated.} = nnkOutTy
|
||||
nnkSharedTy* {.deprecated.} = nnkSinkAsgn
|
||||
|
||||
type
|
||||
NimIdent* {.deprecated.} = object of RootObj
|
||||
## Represents a Nim identifier in the AST. **Note**: This is only
|
||||
## rarely useful, for identifier construction from a string
|
||||
## use `ident"abc"`.
|
||||
|
||||
NimSymObj = object # hidden
|
||||
NimSym* {.deprecated.} = ref NimSymObj
|
||||
## Represents a Nim *symbol* in the compiler; a *symbol* is a looked-up
|
||||
## *ident*.
|
||||
|
||||
|
||||
const
|
||||
nnkLiterals* = {nnkCharLit..nnkNilLit}
|
||||
# see matching set CallNodes below
|
||||
@@ -152,26 +137,10 @@ const
|
||||
nnkCallStrLit, nnkHiddenCallConv}
|
||||
nnkPragmaCallKinds = {nnkExprColonExpr, nnkCall, nnkCallStrLit}
|
||||
|
||||
{.push warnings: off.}
|
||||
|
||||
proc toNimIdent*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect, deprecated:
|
||||
"Deprecated since version 0.18.0: Use 'ident' or 'newIdentNode' instead.".}
|
||||
## Constructs an identifier from the string `s`.
|
||||
|
||||
proc `==`*(a, b: NimIdent): bool {.magic: "EqIdent", noSideEffect, deprecated:
|
||||
"Deprecated since version 0.18.1; Use '==' on 'NimNode' instead.".}
|
||||
## Compares two Nim identifiers.
|
||||
|
||||
proc `==`*(a, b: NimNode): bool {.magic: "EqNimrodNode", noSideEffect.}
|
||||
## Compare two Nim nodes. Return true if nodes are structurally
|
||||
## equivalent. This means two independently created nodes can be equal.
|
||||
|
||||
proc `==`*(a, b: NimSym): bool {.magic: "EqNimrodNode", noSideEffect, deprecated:
|
||||
"Deprecated since version 0.18.1; Use '==(NimNode, NimNode)' instead.".}
|
||||
## Compares two Nim symbols.
|
||||
|
||||
{.pop.}
|
||||
|
||||
proc sameType*(a, b: NimNode): bool {.magic: "SameNodeType", noSideEffect.} =
|
||||
## Compares two Nim nodes' types. Return true if the types are the same,
|
||||
## e.g. true when comparing alias with original type.
|
||||
@@ -252,25 +221,6 @@ proc strVal*(n: NimNode): string {.magic: "NStrVal", noSideEffect.}
|
||||
## See also:
|
||||
## * `strVal= proc<#strVal=,NimNode,string>`_ for setting the string value.
|
||||
|
||||
{.push warnings: off.} # silence `deprecated`
|
||||
|
||||
proc ident*(n: NimNode): NimIdent {.magic: "NIdent", noSideEffect, deprecated:
|
||||
"Deprecated since version 0.18.1; All functionality is defined on 'NimNode'.".}
|
||||
|
||||
proc symbol*(n: NimNode): NimSym {.magic: "NSymbol", noSideEffect, deprecated:
|
||||
"Deprecated since version 0.18.1; All functionality is defined on 'NimNode'.".}
|
||||
|
||||
proc getImpl*(s: NimSym): NimNode {.magic: "GetImpl", noSideEffect, deprecated: "use `getImpl: NimNode -> NimNode` instead".}
|
||||
|
||||
proc `$`*(i: NimIdent): string {.magic: "NStrVal", noSideEffect, deprecated:
|
||||
"Deprecated since version 0.18.1; Use 'strVal' instead.".}
|
||||
## Converts a Nim identifier to a string.
|
||||
|
||||
proc `$`*(s: NimSym): string {.magic: "NStrVal", noSideEffect, deprecated:
|
||||
"Deprecated since version 0.18.1; Use 'strVal' instead.".}
|
||||
## Converts a Nim symbol to a string.
|
||||
|
||||
{.pop.}
|
||||
|
||||
when (NimMajor, NimMinor, NimPatch) >= (1, 3, 5) or defined(nimSymImplTransform):
|
||||
proc getImplTransformed*(symbol: NimNode): NimNode {.magic: "GetImplTransf", noSideEffect.}
|
||||
@@ -373,15 +323,6 @@ proc getTypeImpl*(n: typedesc): NimNode {.magic: "NGetType", noSideEffect.}
|
||||
proc `intVal=`*(n: NimNode, val: BiggestInt) {.magic: "NSetIntVal", noSideEffect.}
|
||||
proc `floatVal=`*(n: NimNode, val: BiggestFloat) {.magic: "NSetFloatVal", noSideEffect.}
|
||||
|
||||
{.push warnings: off.}
|
||||
|
||||
proc `symbol=`*(n: NimNode, val: NimSym) {.magic: "NSetSymbol", noSideEffect, deprecated:
|
||||
"Deprecated since version 0.18.1; Generate a new 'NimNode' with 'genSym' instead.".}
|
||||
|
||||
proc `ident=`*(n: NimNode, val: NimIdent) {.magic: "NSetIdent", noSideEffect, deprecated:
|
||||
"Deprecated since version 0.18.1; Generate a new 'NimNode' with 'ident(string)' instead.".}
|
||||
|
||||
{.pop.}
|
||||
|
||||
proc `strVal=`*(n: NimNode, val: string) {.magic: "NSetStrVal", noSideEffect.}
|
||||
## Sets the string value of a string literal or comment.
|
||||
@@ -464,14 +405,6 @@ proc newFloatLitNode*(f: BiggestFloat): NimNode =
|
||||
result = newNimNode(nnkFloatLit)
|
||||
result.floatVal = f
|
||||
|
||||
{.push warnings: off.}
|
||||
|
||||
proc newIdentNode*(i: NimIdent): NimNode {.deprecated: "use ident(string)".} =
|
||||
## Creates an identifier node from `i`.
|
||||
result = newNimNode(nnkIdent)
|
||||
result.ident = i
|
||||
|
||||
{.pop.}
|
||||
|
||||
proc newIdentNode*(i: string): NimNode {.magic: "StrToIdent", noSideEffect.}
|
||||
## Creates an identifier node from `i`. It is simply an alias for
|
||||
@@ -722,17 +655,6 @@ proc newCall*(theProc: NimNode, args: varargs[NimNode]): NimNode =
|
||||
result.add(theProc)
|
||||
result.add(args)
|
||||
|
||||
{.push warnings: off.}
|
||||
|
||||
proc newCall*(theProc: NimIdent, args: varargs[NimNode]): NimNode {.deprecated:
|
||||
"Deprecated since v0.18.1; use 'newCall(string, ...)' or 'newCall(NimNode, ...)' instead".} =
|
||||
## Produces a new call node. `theProc` is the proc that is called with
|
||||
## the arguments `args[0..]`.
|
||||
result = newNimNode(nnkCall)
|
||||
result.add(newIdentNode(theProc))
|
||||
result.add(args)
|
||||
|
||||
{.pop.}
|
||||
|
||||
proc newCall*(theProc: string,
|
||||
args: varargs[NimNode]): NimNode =
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -1674,7 +1674,6 @@ when not defined(js) and defined(nimV2):
|
||||
when defined(nimTypeNames) or defined(nimArcIds) or defined(nimOrcLeakDetector):
|
||||
name: cstring
|
||||
traceImpl: pointer
|
||||
disposeImpl: pointer
|
||||
typeInfoV1: pointer # for backwards compat, usually nil
|
||||
flags: int
|
||||
when defined(gcDestructors):
|
||||
@@ -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])
|
||||
@@ -3142,27 +3141,3 @@ proc arrayWithDefault*[T](size: static int): array[size, T] {.noinit, nodestroy,
|
||||
## Creates a new array filled with `default(T)`.
|
||||
for i in 0..size-1:
|
||||
result[i] = default(T)
|
||||
|
||||
when hostOS == "standalone":
|
||||
# Include panicoverride.nim late so users can use the full extent of the
|
||||
# language in their custom panic handlers (e.g. macros).
|
||||
# Users define `proc panic(msg: string)` and `proc rawoutput(msg: string)`.
|
||||
include "$projectpath/panicoverride"
|
||||
|
||||
when not declared(panic):
|
||||
{.error:
|
||||
"a panic proc with the following signature must be provided " &
|
||||
"when compiling with --os:standalone: " &
|
||||
"`proc panic(msg: string) {.nimcall.}`".}
|
||||
|
||||
when not declared(rawoutput):
|
||||
{.error:
|
||||
"a rawoutput proc with the following signature must be provided " &
|
||||
"when compiling with --os:standalone: " &
|
||||
"`proc rawoutput(msg: string) {.nimcall.}`".}
|
||||
|
||||
# Wrappers with exportc that fatal.nim references via importc.
|
||||
# This way panicoverride keeps old API and can still be included without
|
||||
# ssymbols being duplicated.
|
||||
proc nimPanic(s: string) {.exportc, noreturn.} = panic(s)
|
||||
proc nimRawoutput(s: string) {.exportc.} = rawoutput(s)
|
||||
|
||||
@@ -477,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) {.
|
||||
@@ -778,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)
|
||||
|
||||
@@ -849,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 <= MemAlign:
|
||||
result = 0
|
||||
else:
|
||||
result = align(sizeof(BigChunk) + sizeof(Cell), alignment) - sizeof(BigChunk) - sizeof(Cell)
|
||||
|
||||
proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = MemAlign): pointer =
|
||||
proc rawAlloc(a: var MemRegion, requestedSize: int): pointer =
|
||||
when defined(nimTypeNames):
|
||||
inc(a.allocCounter)
|
||||
sysAssert(allocInv(a), "rawAlloc: begin")
|
||||
@@ -866,9 +855,7 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = MemAlign):
|
||||
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 <= MemAlign:
|
||||
if size <= SmallChunkSize-smallChunkOverhead():
|
||||
template fetchSharedCells(tc: PSmallChunk) =
|
||||
# Consumes cells from (potentially) foreign threads from `a.sharedFreeLists[s]`
|
||||
when defined(gcDestructors):
|
||||
@@ -963,21 +950,13 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = MemAlign):
|
||||
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):
|
||||
@@ -1088,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)
|
||||
@@ -1114,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!"
|
||||
@@ -1128,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!"
|
||||
|
||||
@@ -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
|
||||
@@ -192,7 +192,7 @@ proc nimRawDispose(p: pointer, alignment: int) {.compilerRtl.} =
|
||||
let hdrSize = align(sizeof(RefHeader), alignment)
|
||||
alignedDealloc(p -! hdrSize, alignment)
|
||||
|
||||
template `deallocRef`*[T](x: owned(ref T)) = nimRawDispose(cast[pointer](x), T.alignOf)
|
||||
template `=dispose`*[T](x: owned(ref T)) = nimRawDispose(cast[pointer](x), T.alignOf)
|
||||
#proc dispose*(x: pointer) = nimRawDispose(x)
|
||||
|
||||
proc nimDestroyAndDispose(p: pointer) {.compilerRtl, quirky, raises: [].} =
|
||||
@@ -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
|
||||
|
||||
@@ -42,13 +42,27 @@ 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
|
||||
|
||||
Cell {.pure.} = object
|
||||
refcount: RefCount # the refcount and some flags
|
||||
typ: PNimType
|
||||
when trackAllocationSource:
|
||||
filename: cstring
|
||||
line: int
|
||||
when useCellIds:
|
||||
id: int
|
||||
|
||||
PCell = ptr Cell
|
||||
|
||||
type
|
||||
PPageDesc = ptr PageDesc
|
||||
@@ -64,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,23 +14,14 @@ const
|
||||
quirkyExceptions = compileOption("exceptions", "quirky")
|
||||
|
||||
when hostOS == "standalone":
|
||||
# These procs are defined in panicoverride.nim, which gets included at end
|
||||
# of system.nim with exportc.
|
||||
proc nimPanic(msg: string) {.importc: "nimPanic", noreturn.}
|
||||
proc nimRawoutput(msg: string) {.importc: "nimRawoutput".}
|
||||
include "$projectpath/panicoverride"
|
||||
|
||||
proc sysFatal(exceptn: typedesc[Defect], message: string) {.inline, noreturn, raises: [], tags: [].} =
|
||||
{.cast(noSideEffect).}:
|
||||
{.cast(raises: []).}:
|
||||
{.cast(tags: []).}:
|
||||
nimPanic(message)
|
||||
func sysFatal(exceptn: typedesc[Defect], message: string) {.inline.} =
|
||||
panic(message)
|
||||
|
||||
proc sysFatal(exceptn: typedesc[Defect], message, arg: string) {.inline, noreturn, raises: [], tags: [].} =
|
||||
{.cast(noSideEffect).}:
|
||||
{.cast(raises: []).}:
|
||||
{.cast(tags: []).}:
|
||||
nimRawoutput(message)
|
||||
nimPanic(arg)
|
||||
func sysFatal(exceptn: typedesc[Defect], message, arg: string) {.inline.} =
|
||||
rawoutput(message)
|
||||
panic(arg)
|
||||
|
||||
elif quirkyExceptions and not defined(nimscript):
|
||||
import ansi_c
|
||||
|
||||
@@ -458,12 +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: max(typ.base.align, MemAlign) else: MemAlign
|
||||
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
|
||||
gcAssert((cast[int](cellToUsr(res)) and (alignment-1)) == 0, "newObj: 2")
|
||||
gcAssert((cast[int](res) and (MemAlign-1)) == 0, "newObj: 2")
|
||||
# now it is buffered in the ZCT
|
||||
res.typ = typ
|
||||
setFrameInfo(res)
|
||||
@@ -511,12 +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.base != nil: max(typ.base.align, MemAlign) else: MemAlign
|
||||
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
|
||||
sysAssert((cast[int](cellToUsr(res)) and (alignment-1)) == 0, "newObj: 2")
|
||||
sysAssert((cast[int](res) and (MemAlign-1)) == 0, "newObj: 2")
|
||||
# now it is buffered in the ZCT
|
||||
res.typ = typ
|
||||
setFrameInfo(res)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -38,21 +38,6 @@ type
|
||||
PByte = ptr ByteArray
|
||||
PString = ptr string
|
||||
|
||||
when not defined(nimV2):
|
||||
type
|
||||
RefCount = int
|
||||
|
||||
Cell {.pure.} = object
|
||||
refcount: RefCount # the refcount and some flags
|
||||
typ: PNimType
|
||||
when trackAllocationSource:
|
||||
filename: cstring
|
||||
line: int
|
||||
when useCellIds:
|
||||
id: int
|
||||
|
||||
PCell = ptr Cell
|
||||
|
||||
when declared(IntsPerTrunk):
|
||||
discard
|
||||
else:
|
||||
|
||||
@@ -302,7 +302,7 @@ proc collectColor(s: Cell; desc: PNimTypeV2; col: int; j: var GcEnv) =
|
||||
while j.traceStack.len > 0:
|
||||
let (entry, desc) = j.traceStack.pop()
|
||||
let t = head entry[]
|
||||
entry[] = nil # ensure that the destructor does not touch moribund objects!
|
||||
entry[] = nil # ensure that the destructor does touch moribund objects!
|
||||
if t.color == col and t.rootIdx == 0:
|
||||
j.toFree.add(t, desc)
|
||||
t.setColor(colBlack)
|
||||
@@ -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))
|
||||
|
||||
@@ -1,599 +0,0 @@
|
||||
#
|
||||
# YRC: Thread-safe ORC (concurrent cycle collector).
|
||||
# Same API as orc.nim but with striped queues and global lock for merge/collect.
|
||||
# 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.
|
||||
#
|
||||
# ## Key Invariant: Topology vs. Reference Counts
|
||||
#
|
||||
# Only `obj.field = x` can change the topology of the heap graph (heap-to-heap
|
||||
# edges). Local variable assignments (`var local = someRef`) affect reference
|
||||
# counts but never create heap-to-heap edges and thus cannot create cycles.
|
||||
#
|
||||
# The actual pointer write in `obj.field = x` happens immediately and lock-free —
|
||||
# the graph topology is always up-to-date in memory. Only the RC adjustments are
|
||||
# deferred: increments and decrements are buffered into per-stripe queues
|
||||
# (`toInc`, `toDec`) protected by fine-grained per-stripe locks.
|
||||
#
|
||||
# When `collectCycles` runs it takes the global lock, drains all stripe buffers
|
||||
# via `mergePendingRoots`, and then traces the physical pointer graph (via
|
||||
# `traceImpl`) to detect cycles. This is sound because `trace` follows the actual
|
||||
# pointer values in memory — which are always current — and uses the reconciled
|
||||
# RCs only to identify candidate roots and confirm garbage.
|
||||
#
|
||||
# In summary: the physical pointer graph is always consistent (writes are
|
||||
# immediate); only the reference counts are eventually consistent (writes are
|
||||
# buffered). The per-stripe locks are cheap; the expensive global lock is only
|
||||
# needed when interpreting the RCs during collection.
|
||||
#
|
||||
# ## 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 because 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, which means A has an external reference
|
||||
# (from the stack) that is not a heap-to-heap edge. During trial deletion
|
||||
# (`markGray`) only internal edges are subtracted from RCs, so A's external
|
||||
# reference survives, `scan` finds A's RC >= 0, calls `scanBlack`, and rescues A
|
||||
# and everything reachable from it — including B. In short: the mutator can only
|
||||
# modify objects it can reach, but the cycle collector only frees objects nothing
|
||||
# external can reach. The two conditions are mutually exclusive.
|
||||
#
|
||||
#[
|
||||
|
||||
The problem described in Bacon01 is: during markGray/scan, a mutator concurrently
|
||||
does X.field = Z (was X→Y), changing the physical graph while the collector is tracing
|
||||
it. The collector might see stale or new edges. The reasons this is still safe:
|
||||
|
||||
Stale edges cancel with unbuffered decrements: If the collector sees old edge X→Y
|
||||
(mutator already wrote X→Z and buffered dec(Y)), the phantom trial deletion and the
|
||||
unbuffered dec cancel — Y's effective RC is correct.
|
||||
|
||||
scanBlack rescues via current physical edges: If X has external refs (merged RC reflects
|
||||
the mutator's access), scanBlack(X) re-traces X and follows the current physical edge X→Z,
|
||||
incrementing Z's RC and marking it black. Z survives.
|
||||
|
||||
rcSum==edges fast path is conservative: Any discrepancy between physical graph and merged
|
||||
state (stale or new edges) causes rcSum != edges, falling back to the slow path which
|
||||
rescues anything with RC >= 0.
|
||||
|
||||
Unreachable cycles are truly unreachable: The mutator can only reach objects through chains
|
||||
rooted in merged references. If a cycle has zero external refs at merge time, no mutator
|
||||
can reach it.
|
||||
|
||||
]#
|
||||
|
||||
{.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, benign, raises: [].}
|
||||
DisposeProc = proc (p: pointer) {.nimcall, benign, raises: [].}
|
||||
|
||||
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
|
||||
|
||||
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))
|
||||
|
||||
include threadids
|
||||
|
||||
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, benign, 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
|
||||
gYrcGlobalLock: Lock
|
||||
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(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:
|
||||
withLock gYrcGlobalLock:
|
||||
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:
|
||||
withLock gYrcGlobalLock:
|
||||
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() =
|
||||
for i in 0..<NumStripes:
|
||||
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]
|
||||
c.rc = c.rc -% rcIncrement
|
||||
if (c.rc and inRootsFlag) == 0:
|
||||
c.rc = c.rc or 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, s.rc shr rcShift, s.color)
|
||||
if (s.rc and inRootsFlag) == 0:
|
||||
let p = s +! sizeof(RefHeader)
|
||||
when logOrc: writeCell("free", s, desc)
|
||||
if desc.disposeImpl != nil:
|
||||
cast[DestructorProc](desc.disposeImpl)(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[]
|
||||
t.rc = t.rc +% rcIncrement
|
||||
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 +% (s.rc 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[]
|
||||
t.rc = t.rc -% rcIncrement
|
||||
j.edges = j.edges +% 1
|
||||
if t.color != colGray:
|
||||
t.setColor colGray
|
||||
j.touched = j.touched +% 1
|
||||
j.rcSum = j.rcSum +% (t.rc shr rcShift) +% 2
|
||||
trace(t, desc, j)
|
||||
|
||||
proc scan(s: Cell; desc: PNimTypeV2; j: var GcEnv) =
|
||||
if s.color == colGray:
|
||||
if (s.rc 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 (t.rc 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 (s.rc 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 (t.rc and inRootsFlag) == 0:
|
||||
j.toFree.add(t, desc)
|
||||
t.setColor(colBlack)
|
||||
trace(t, desc, j)
|
||||
|
||||
proc collectCyclesBacon(j: var GcEnv; lowMark: int) =
|
||||
let last = roots.len -% 1
|
||||
when logOrc:
|
||||
for i in countdown(last, lowMark):
|
||||
writeCell("root", roots.d[i][0], roots.d[i][1])
|
||||
init j.toFree
|
||||
|
||||
# First pass: swap roots with rc <= 0 to the end for immediate freeing
|
||||
# Check RC before markGray modifies it. Use a while loop that shrinks as we iterate.
|
||||
var cycleStart = lowMark
|
||||
var immediateFreeStart = roots.len
|
||||
while cycleStart < immediateFreeStart:
|
||||
let s = roots.d[cycleStart][0]
|
||||
if (s.rc shr rcShift) < 0:
|
||||
# Root is already garbage, swap to end for immediate freeing
|
||||
dec immediateFreeStart
|
||||
swap(roots.d[cycleStart], roots.d[immediateFreeStart])
|
||||
when logOrc: writeCell("root swapped to end for immediate free (rc <= 0)", roots.d[immediateFreeStart][0], roots.d[immediateFreeStart][1])
|
||||
else:
|
||||
inc cycleStart
|
||||
|
||||
# Second pass: process remaining roots (rc > 0) for cycle detection
|
||||
# Only process roots from lowMark to immediateFreeStart (cycleStart == immediateFreeStart after swap loop)
|
||||
for i in lowMark..<immediateFreeStart:
|
||||
markGray(roots.d[i][0], roots.d[i][1], j)
|
||||
var colToCollect = colWhite
|
||||
if j.rcSum == j.edges:
|
||||
colToCollect = colGray
|
||||
j.keepThreshold = true
|
||||
else:
|
||||
for i in lowMark..<immediateFreeStart:
|
||||
scan(roots.d[i][0], roots.d[i][1], j)
|
||||
for i in lowMark..<immediateFreeStart:
|
||||
let s = roots.d[i][0]
|
||||
s.rc = s.rc and not inRootsFlag
|
||||
collectColor(s, roots.d[i][1], colToCollect, j)
|
||||
when not defined(nimStressOrc):
|
||||
let oldThreshold = rootsThreshold
|
||||
rootsThreshold = high(int)
|
||||
|
||||
# Prepare immediate-free roots for freeing: recursively trace through ALL descendants
|
||||
# and set child pointers to nil, just like collectColor does. This prevents destructors
|
||||
# from accessing children and triggering nested collectCycles().
|
||||
# Add them to j.toFree so they're freed together after roots.len = 0 is set.
|
||||
# Keep inRootsFlag set until right before freeing to prevent mergePendingRoots from
|
||||
# accessing freed cells during nested collectCycles().
|
||||
let immediateFreeCount = roots.len - immediateFreeStart
|
||||
for i in immediateFreeStart..<roots.len:
|
||||
let s = roots.d[i][0]
|
||||
let desc = roots.d[i][1]
|
||||
# Don't clear inRootsFlag yet - keep it set so mergePendingRoots can skip this cell
|
||||
orcAssert(j.traceStack.len == 0, "trace stack not empty before preparing immediate-free root")
|
||||
s.setColor(colBlack)
|
||||
j.toFree.add(s, desc)
|
||||
trace(s, desc, j)
|
||||
# Recursively trace and nil ALL descendants, just like collectColor does
|
||||
# This ensures destructors can't access any children, preventing nested collections
|
||||
while j.traceStack.len > 0:
|
||||
let (entry, childDesc) = j.traceStack.pop()
|
||||
let t = head entry[]
|
||||
entry[] = nil
|
||||
# Recursively trace children to nil their descendants too
|
||||
trace(t, childDesc, j)
|
||||
|
||||
# Clear roots before freeing to prevent nested collectCycles() from accessing freed cells
|
||||
roots.len = 0
|
||||
|
||||
# Free all roots (both immediate-free and cycle-detected) together
|
||||
# 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]
|
||||
s.rc = s.rc and not inRootsFlag
|
||||
when orcLeakDetector:
|
||||
writeCell("CYCLIC OBJECT FREED", s, j.toFree.d[i][1])
|
||||
free(s, j.toFree.d[i][1])
|
||||
when not defined(nimStressOrc):
|
||||
rootsThreshold = oldThreshold
|
||||
j.freed = j.freed +% j.toFree.len +% immediateFreeCount
|
||||
deinit j.toFree
|
||||
|
||||
when defined(nimOrcStats):
|
||||
var freedCyclicObjects {.threadvar.}: int
|
||||
|
||||
proc collectCycles() =
|
||||
when logOrc:
|
||||
cfprintf(cstderr, "[collectCycles] begin\n")
|
||||
withLock gYrcGlobalLock:
|
||||
mergePendingRoots()
|
||||
if roots.len >= RootsThreshold and mayRunCycleCollect():
|
||||
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
|
||||
# Cap growth so threshold doesn't grow without bound when we rarely free cycles
|
||||
#rootsThreshold = min(rootsThreshold, defaultThreshold *% 16)
|
||||
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* =
|
||||
withLock gYrcGlobalLock:
|
||||
mergePendingRoots()
|
||||
if 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.} =
|
||||
withLock gYrcGlobalLock:
|
||||
mergePendingRoots()
|
||||
result = roots.len
|
||||
|
||||
proc GC_partialCollect*(limit: int) =
|
||||
withLock gYrcGlobalLock:
|
||||
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.
|
||||
## Atomically stores src into dest, then buffers RC adjustments.
|
||||
## Freeing is always done by the cycle collector, never inline.
|
||||
let tmp = dest[]
|
||||
atomicStoreN(dest, src, ATOMIC_RELEASE)
|
||||
if src != nil:
|
||||
nimIncRefCyclic(src, true)
|
||||
if tmp != nil:
|
||||
yrcDec(tmp, desc)
|
||||
|
||||
proc nimSinkYrc(dest: ptr pointer; src: pointer; desc: PNimTypeV2) {.compilerRtl.} =
|
||||
## YRC write barrier for ref sink (move). No incRef on source.
|
||||
## Freeing is always done by the cycle collector, never inline.
|
||||
let tmp = dest[]
|
||||
atomicStoreN(dest, src, ATOMIC_RELEASE)
|
||||
if tmp != nil:
|
||||
yrcDec(tmp, desc)
|
||||
|
||||
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
|
||||
initLock(gYrcGlobalLock)
|
||||
for i in 0..<NumStripes:
|
||||
when not defined(yrcAtomics):
|
||||
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,761 +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
|
||||
|
||||
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)
|
||||
|
||||
\* 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
|
||||
|
||||
\* 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
|
||||
globalLock, \* thread holding global 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
|
||||
|
||||
\* 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"}])
|
||||
|
||||
\* 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>>
|
||||
|
||||
\* ============================================================================
|
||||
\* 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>>
|
||||
|
||||
\* ============================================================================
|
||||
\* 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>>
|
||||
|
||||
\* 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>>
|
||||
|
||||
\* ============================================================================
|
||||
\* 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>>
|
||||
|
||||
\* ============================================================================
|
||||
\* 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>>
|
||||
|
||||
\* ============================================================================
|
||||
\* 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>>
|
||||
|
||||
\* ============================================================================
|
||||
\* 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>>
|
||||
|
||||
\* ============================================================================
|
||||
\* 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>>
|
||||
|
||||
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>>
|
||||
|
||||
\* ============================================================================
|
||||
\* 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>>
|
||||
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
|
||||
|
||||
\* ============================================================================
|
||||
\* Collector Actions
|
||||
\* ============================================================================
|
||||
|
||||
\* Collector acquires global lock for entire collection cycle
|
||||
CollectorAcquireLock(thread) ==
|
||||
/\ globalLock = NULL
|
||||
/\ globalLock' = thread
|
||||
/\ UNCHANGED <<edges, roots, rc, color, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, mergedRoots, collecting, gcEnv, pendingWrites>>
|
||||
|
||||
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>>
|
||||
|
||||
\* ============================================================================
|
||||
\* 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)
|
||||
|
||||
\* ============================================================================
|
||||
\* 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 = {}
|
||||
/\ 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
|
||||
|
||||
\* ============================================================================
|
||||
\* Specification
|
||||
\* ============================================================================
|
||||
|
||||
Spec == Init /\ [][Next]_<<edges, roots, rc, color, inRoots, toIncLen, toInc, toDecLen, toDec, lockInc, lockDec, globalLock, mergedRoots, collecting, gcEnv, pendingWrites>>
|
||||
|
||||
THEOREM Spec => []Safety
|
||||
THEOREM Spec => []RCInvariant
|
||||
THEOREM Spec => []CycleInvariant
|
||||
|
||||
====
|
||||
@@ -237,7 +237,7 @@ proc clearInstCache(graph: ModuleGraph, projectFileIdx: FileIndex) =
|
||||
for tbl in mitems(graph.attachedOps):
|
||||
var attachedOpsToDelete = newSeq[ItemId]()
|
||||
for id in tbl.keys:
|
||||
if id.module == projectFileIdx.int and sfOverridden in tbl[id].flags:
|
||||
if id.module == projectFileIdx.int and sfOverridden in resolveAttachedOp(graph, tbl[id]).flags:
|
||||
attachedOpsToDelete.add id
|
||||
for id in attachedOpsToDelete:
|
||||
tbl.del id
|
||||
|
||||
@@ -27,7 +27,6 @@ const
|
||||
"io",
|
||||
"js",
|
||||
"ic",
|
||||
"ic_disabled",
|
||||
"lib",
|
||||
"manyloc",
|
||||
"nimble-packages",
|
||||
@@ -490,16 +489,46 @@ proc testNimblePackages(r: var TResults; cat: Category; packageFilter: string) =
|
||||
|
||||
proc icTests(r: var TResults; testsDir: string, cat: Category, options: string;
|
||||
isNavigatorTest: bool) =
|
||||
template editedTest() =
|
||||
var test = makeTest(file, options, cat)
|
||||
test.spec.targets = {targetC}
|
||||
test.spec.cmd = compilerPrefix & " ic --hint:Conf:off --warnings:off $options " & file
|
||||
const
|
||||
tooltests = ["compiler/nim.nim"]
|
||||
writeOnly = " --incremental:writeonly "
|
||||
readOnly = " --incremental:readonly "
|
||||
incrementalOn = " --incremental:legacy -d:nimIcIntegrityChecks "
|
||||
navTestConfig = " --ic:legacy -d:nimIcNavigatorTests --hint:Conf:off --warnings:off "
|
||||
|
||||
template test(x: untyped) =
|
||||
testSpecWithNimcache(r, makeRawTest(file, x & options, cat), nimcache)
|
||||
|
||||
template editedTest(x: untyped) =
|
||||
var test = makeTest(file, x & options, cat)
|
||||
if isNavigatorTest:
|
||||
test.spec.action = actionCompile
|
||||
test.spec.targets = {getTestSpecTarget()}
|
||||
testSpecWithNimcache(r, test, nimcache)
|
||||
|
||||
template checkTest() =
|
||||
var test = makeRawTest(file, options, cat)
|
||||
test.spec.cmd = compilerPrefix & " check --hint:Conf:off --warnings:off --ic:legacy $options " & file
|
||||
testSpecWithNimcache(r, test, nimcache)
|
||||
|
||||
if not isNavigatorTest:
|
||||
for file in tooltests:
|
||||
let nimcache = nimcacheDir(file, options, getTestSpecTarget())
|
||||
removeDir(nimcache)
|
||||
|
||||
let oldPassed = r.passed
|
||||
checkTest()
|
||||
|
||||
if r.passed == oldPassed+1:
|
||||
checkTest()
|
||||
if r.passed == oldPassed+2:
|
||||
checkTest()
|
||||
|
||||
const tempExt = "_temp.nim"
|
||||
for it in walkDirRec(testsDir):
|
||||
# for it in ["tests/ic/timports.nim"]: # debugging: to try a specific test
|
||||
if isTestFile(it) and not it.endsWith(tempExt):
|
||||
let nimcache = nimcacheDir(it, options, targetC)
|
||||
let nimcache = nimcacheDir(it, options, getTestSpecTarget())
|
||||
removeDir(nimcache)
|
||||
|
||||
let content = readFile(it)
|
||||
@@ -507,7 +536,7 @@ proc icTests(r: var TResults; testsDir: string, cat: Category, options: string;
|
||||
let file = it.replace(".nim", tempExt)
|
||||
writeFile(file, fragment)
|
||||
let oldPassed = r.passed
|
||||
editedTest()
|
||||
editedTest(if isNavigatorTest: navTestConfig else: incrementalOn)
|
||||
if r.passed != oldPassed+1: break
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
@@ -43,7 +43,7 @@ when not defined(arm64):
|
||||
pkg "awk"
|
||||
pkg "bigints"
|
||||
pkg "binaryheap", "nim c -r binaryheap.nim"
|
||||
pkg "BipBuffer"
|
||||
pkg "BipBuffer", url = "https://github.com/nim-lang/BipBuffer"
|
||||
pkg "bncurve"
|
||||
pkg "brainfuck", "nim c -d:release -r tests/compile.nim"
|
||||
pkg "c2nim", "nim c testsuite/tester.nim"
|
||||
@@ -112,7 +112,7 @@ else:
|
||||
pkg "nimcrypto", "nim r --path:. tests/testall.nim" # `--path:.` workaround needed, see D20210308T165435
|
||||
pkg "NimData", "nim c -o:nimdataa src/nimdata.nim"
|
||||
pkg "nimes", "nim c src/nimes.nim"
|
||||
pkg "nimfp", "nim c -o:nfp -r src/fp.nim"
|
||||
# pkg "nimfp", "nim c -o:nfp -r src/fp.nim"
|
||||
pkg "nimgame2", "nim c --mm:refc nimgame2/nimgame.nim"
|
||||
pkg "nimgen", "nim c -o:nimgenn -r src/nimgen/runcfg.nim"
|
||||
pkg "nimib"
|
||||
@@ -168,7 +168,7 @@ pkg "taskpools"
|
||||
pkg "telebot", "nim c -o:tbot -r src/telebot.nim"
|
||||
pkg "tempdir"
|
||||
pkg "templates"
|
||||
pkg "tensordsl", "nim c -r --mm:refc tests/tests.nim", "https://krux02@bitbucket.org/krux02/tensordslnim.git"
|
||||
# pkg "tensordsl", "nim c -r --mm:refc tests/tests.nim", "https://krux02@bitbucket.org/krux02/tensordslnim.git"
|
||||
pkg "terminaltables", "nim c src/terminaltables.nim"
|
||||
pkg "termstyle", "nim c -r termstyle.nim"
|
||||
pkg "testutils"
|
||||
|
||||
@@ -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,104 +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)
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ proc exit(code: cint) {.importc, header:"stdlib.h".}
|
||||
|
||||
{.push stack_trace: off, profiler:off.}
|
||||
|
||||
proc rawoutput(s: string) =
|
||||
printf("RAW: %s\n", s.cstring)
|
||||
|
||||
proc panic(s: string) {.noreturn.} =
|
||||
printf("PANIC: %s\n", s.cstring)
|
||||
proc rawoutput(s: cstring) =
|
||||
printf("RAW: %s\n", s)
|
||||
|
||||
proc panic(s: cstring) {.noreturn.} =
|
||||
printf("PANIC: %s\n", s)
|
||||
exit(0)
|
||||
|
||||
{.pop.}
|
||||
@@ -4,9 +4,9 @@ proc exit(code: int) {.importc, header: "<stdlib.h>", cdecl.}
|
||||
{.push stack_trace: off, profiler:off.}
|
||||
|
||||
proc rawoutput(s: string) =
|
||||
printf("%s\n", s.cstring)
|
||||
printf("%s\n", s)
|
||||
|
||||
proc panic(s: string) {.noreturn.} =
|
||||
proc panic(s: string) =
|
||||
rawoutput(s)
|
||||
exit(1)
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
proc v[T](_: typedesc[T]): int =
|
||||
if T is int64: 6 else: 4
|
||||
|
||||
type
|
||||
D*[T] = object
|
||||
c*: seq[T]
|
||||
k*: array[v(T), int]
|
||||
F = distinct int64
|
||||
W* = object
|
||||
y: D[F]
|
||||
j*: D[int64]
|
||||
@@ -1,8 +0,0 @@
|
||||
import ./g
|
||||
export g
|
||||
|
||||
proc a*(): W =
|
||||
var e = D[int64]()
|
||||
e.c.setLen(8)
|
||||
e.k[1] = 0
|
||||
result = W(j: e)
|
||||
@@ -1,10 +0,0 @@
|
||||
discard """
|
||||
targets: "c cpp"
|
||||
matrix: "-d:checkAbi"
|
||||
"""
|
||||
|
||||
import ./m25459/h
|
||||
|
||||
for _ in 0 ..< 500:
|
||||
let u = new W
|
||||
u[] = a()
|
||||
@@ -1,31 +0,0 @@
|
||||
discard """
|
||||
targets: "c cpp"
|
||||
matrix: "-d:checkAbi"
|
||||
"""
|
||||
|
||||
proc v[T](_: typedesc[T]): int =
|
||||
if T is int64: 2 else: 1
|
||||
|
||||
type
|
||||
D[T] = object
|
||||
k: array[v(T), int]
|
||||
E[T] = object
|
||||
k: array[v(T), int]
|
||||
F = distinct int64
|
||||
W = object
|
||||
a: D[int64]
|
||||
b: D[F]
|
||||
|
||||
proc csizeof[T](x {.bycopy.} : T): cint {.importc: "sizeof", nodecl.}
|
||||
|
||||
var w: W
|
||||
assert sizeof(w) == csizeof(w)
|
||||
|
||||
var
|
||||
e0: E[F]
|
||||
e1: E[int64]
|
||||
assert sizeof(e0) == csizeof(e0)
|
||||
assert sizeof(e1) == csizeof(e1)
|
||||
|
||||
var tup: (E[F], E[int64])
|
||||
assert sizeof(tup) == csizeof(tup)
|
||||
@@ -57,7 +57,7 @@ proc `=destroy`(t: var Tree) {.nodestroy.} =
|
||||
let x = s.pop
|
||||
if x.left != nil: s.add(x.left)
|
||||
if x.right != nil: s.add(x.right)
|
||||
deallocRef(x)
|
||||
`=dispose`(x)
|
||||
`=destroy`(s)
|
||||
|
||||
proc hasValue(self: var Tree, x: int32): bool =
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
discard """
|
||||
output: '''
|
||||
copy!
|
||||
copy!
|
||||
3
|
||||
2
|
||||
'''
|
||||
"""
|
||||
|
||||
type Foo = distinct int
|
||||
|
||||
var counter = 0
|
||||
|
||||
proc `=destroy`(pkt: var Foo) =
|
||||
if cast[int](pkt) != 0:
|
||||
echo cast[int](pkt)
|
||||
|
||||
proc `=copy`(a: var Foo, b: Foo) =
|
||||
if cast[int](a) == cast[int](b):
|
||||
return
|
||||
|
||||
`=destroy`(a)
|
||||
if cast[int](b) == 0:
|
||||
zeroMem(addr a, sizeof(Foo))
|
||||
else:
|
||||
counter += 1
|
||||
copyMem(addr a, addr counter, sizeof(Foo))
|
||||
echo "copy!"
|
||||
|
||||
proc makeFoo(): Foo =
|
||||
counter += 1
|
||||
cast[Foo](counter)
|
||||
|
||||
|
||||
type Bar = object
|
||||
val: Foo
|
||||
|
||||
|
||||
proc consume(x: sink Bar) =
|
||||
discard
|
||||
|
||||
let x = Bar(val: makeFoo())
|
||||
consume(x)
|
||||
discard x
|
||||
@@ -1,5 +1,5 @@
|
||||
discard """
|
||||
errormsg: "ValueError can raise an unlisted exception: ValueError"
|
||||
errormsg: "can raise an unlisted exception: Exception"
|
||||
line: 10
|
||||
"""
|
||||
{.push warningAsError[Effect]: on.}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user