Merge branch 'devel' into pr_leak2_re

This commit is contained in:
ringabout
2026-08-18 20:02:43 +08:00
committed by GitHub
60 changed files with 706 additions and 644 deletions

View File

@@ -25,6 +25,11 @@ type
pfStructural ## use structural prefix-chain detection and tree-walk
pfBidirectional ## also check reverse direction per field in nkObjConstr
proc isCompileTimeOnlyNode(n: PNode): bool {.inline.} =
## `typeof` and typedesc/static values describe types at compile time; they
## do not read the runtime location that alias analysis is protecting.
n.kind == nkTypeOfExpr or (n.typ != nil and n.typ.isCompileTimeOnly)
func sameLocation(a, b: PNode): bool =
template sameConstIndex(a, b: PNode): bool =
a.kind in nkLiterals and b.kind in nkLiterals and a.intVal == b.intVal
@@ -157,10 +162,13 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
##
## x[] ?<| y depending on type
## ```
if a.isCompileTimeOnlyNode or b.isCompileTimeOnlyNode:
return arNo
if a.kind == b.kind:
case a.kind
of nkSym:
const varKinds = {skVar, skTemp, skProc, skFunc}
const varKinds = {skVar, skTemp, skResult, skProc, skFunc}
# same symbol: aliasing:
if a.sym.id == b.sym.id: result = arYes
elif a.sym.kind in varKinds or b.sym.kind in varKinds:
@@ -271,6 +279,11 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult =
of nkCallKinds:
result = arNo
for i in 1..<b.len:
# A call such as `fill(typeof(result.f))` has a compile-time-only
# argument. It must not make the object constructor look aliased with
# `result.f`; runtime arguments remain subject to the normal analysis.
if b[i].isCompileTimeOnlyNode:
continue
let res = isPartOf(a, b[i], flags)
if res != arNo:
result = res

View File

@@ -2901,22 +2901,6 @@ proc genDestroy(p: BProc; n: PNode) =
internalError(p.config, n.info, "destructor turned out to be not trivial")
discard "ignore calls to the default destructor"
proc genDispose(p: BProc; n: PNode) =
when false:
let elemType = n[1].typ.skipTypes(abstractVar).elementType
var a: TLoc = initLocExpr(p, n[1].skipAddr)
if isFinal(elemType):
if elemType.destructor != nil:
var destroyCall = newNodeI(nkCall, n.info)
genStmts(p, destroyCall)
lineFmt(p, cpsStmts, "#nimRawDispose($1, NIM_ALIGNOF($2))", [rdLoc(a), getTypeDesc(p.module, elemType)])
else:
# ``nimRawDisposeVirtual`` calls the ``finalizer`` which is the same as the
# destructor, but it uses the runtime type. Afterwards the memory is freed:
lineCg(p, cpsStmts, ["#nimDestroyAndDispose($#)", rdLoc(a)])
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
@@ -3490,7 +3474,6 @@ proc genConstHeader(m, q: BModule; p: BProc, sym: PSym) =
m.initProc.procSec(cpsLocals).add('\t')
m.initProc.procSec(cpsLocals).addAssignmentWithValue(sym.loc.snippet):
m.initProc.procSec(cpsLocals).addCast(ptrType(getTypeDesc(m, sym.loc.t, dkVar))):
var getGlobalCall: CallBuilder
m.initProc.procSec(cpsLocals).addCall("hcrGetGlobal",
getModuleDllPath(q, sym),
'"' & sym.loc.snippet & '"')
@@ -3768,7 +3751,6 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
if delayedCodegen(p.module):
genConstStmt(p, n)
else: # enforce addressable consts for exportc
let m = p.module
for it in n:
let symNode = skipPragmaExpr(it.firstSon)
if symNode.kind == nkSym and sfExportc in symNode.sym.flags:

View File

@@ -28,9 +28,6 @@ proc detectStrVersion(m: BModule): int =
else:
detectVersion(strVersion, "nimStrVersion")
proc detectSeqVersion(m: BModule): int =
detectVersion(seqVersion, "nimSeqVersion")
# ----- Version 1: GC'ed strings and seqs --------------------------------
proc genStringLiteralDataOnlyV1(m: BModule, s: string; result: var Rope) =
@@ -132,25 +129,6 @@ proc genStringLiteralV2Const(m: BModule; n: PNode; isConst: bool; result: var Bu
result.addField(strInit, name = "p"):
result.add(cCast(ptrType("NimStrPayload"), cAddr(pureLit)))
proc ssoCharLit(ch: char): string =
## Return a C char literal for ch, with proper escaping.
const hexDigits = "0123456789abcdef"
result = "'"
case ch
of '\'': result.add("\\'")
of '\\': result.add("\\\\")
of '\0': result.add("\\0")
of '\n': result.add("\\n")
of '\r': result.add("\\r")
of '\t': result.add("\\t")
elif ch.ord < 32 or ch.ord == 127:
result.add("\\x")
result.add(hexDigits[ch.ord shr 4])
result.add(hexDigits[ch.ord and 0xf])
else:
result.add(ch)
result.add('\'')
proc ssoBytesLit(m: BModule; s: string; slen: int): string =
## Compute the `bytes` field value for the new SmallString layout.
## byte 0 = slen, bytes 1-7 = inline chars 0-6 (zero-padded).
@@ -320,19 +298,6 @@ proc genStringLiteralV3(m: BModule; n: PNode; isConst: bool; result: var Builder
# ------ Version selector ---------------------------------------------------
proc genStringLiteralDataOnly(m: BModule; s: string; info: TLineInfo;
isConst: bool; result: var Rope) =
case detectStrVersion(m)
of 0, 1: genStringLiteralDataOnlyV1(m, s, result)
of 2:
let tmp = getTempName(m)
genStringLiteralDataOnlyV2(m, s, tmp, isConst)
result.add tmp
of 3:
localError(m.config, info, "genStringLiteralDataOnly not supported for SmallString (nimsso)")
else:
localError(m.config, info, "cannot determine how to produce code for string literal")
proc genNilStringLiteral(m: BModule; info: TLineInfo; result: var Builder) =
result.add(cCast(ptrType(cgsymValue(m, "NimStringDesc")), NimNil))

View File

@@ -75,6 +75,23 @@ proc specializeResetT(p: BProc, accessor: Rope, typ: PType) =
cSizeof(getTypeDesc(p.module, typ)))
else:
specializeResetN(p, accessor, typ.n, typ)
if isCaseObj(typ.n):
# The active branch was released above. Clear the complete object so
# stale bytes from overlapping branches cannot be traced by the GC.
# type
# Foo = object
# case kind: bool
# of true:
# a: ref Bar # 8 bytes (pointer)
# of false:
# b: int # 4 bytes
# specializeResetT for b emits accessor.b = 0 — writes 4 bytes
# But the union is 8 bytes wide (sized by the largest branch)
# The remaining 4 bytes where a used to live are untouched
# Those stale bytes could contain a heap pointer the GC traces → crash
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimZeroMem"),
cCast(CPointer, cAddr(accessor)),
cSizeof(getTypeDesc(p.module, typ)))
of tyTuple:
let typ = getUniqueType(typ)
for i, a in typ.ikids:

View File

@@ -1340,92 +1340,6 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
linefmt(p, cpsStmts, "if (T$1_) std::rethrow_exception(T$1_);$n", [etmp])
endSimpleBlock(p, scope)
proc genTryCppOld(p: BProc, t: PNode, d: var TLoc) =
# There are two versions we generate, depending on whether we
# catch C++ exceptions, imported via .importcpp or not. The
# code can be easier if there are no imported C++ exceptions
# to deal with.
# code to generate:
#
# try
# {
# myDiv(4, 9);
# } catch (NimExceptionType1&) {
# body
# } catch (NimExceptionType2&) {
# finallyPart()
# raise;
# }
# catch(...) {
# general_handler_body
# }
# finallyPart();
template genExceptBranchBody(body: PNode) {.dirty.} =
genRestoreFrameAfterException(p)
expr(p, body, d)
if not isEmptyType(t.typ) and d.k == locNone:
d = getTemp(p, t.typ)
genLineDir(p, t)
cgsym(p.module, "popCurrentExceptionEx")
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural))
startBlockWith(p):
p.s(cpsStmts).add("try {\n")
expr(p, t.firstSon, d)
endBlockWith(p):
p.s(cpsStmts).add("}\n")
var catchAllPresent = false
p.nestedTryStmts[^1].inExcept = true
for i in 1..<t.len:
if t[i].kind != nkExceptBranch: break
# bug #4230: avoid false sharing between branches:
if d.k == locTemp and isEmptyType(t.typ): d.k = locNone
if t[i].len == 1:
# general except section:
catchAllPresent = true
startBlockWith(p):
p.s(cpsStmts).add("catch (...) {\n")
genExceptBranchBody(t[i].firstSon)
endBlockWith(p):
p.s(cpsStmts).add("}\n")
else:
for j in 0..<t[i].len-1:
if t[i][j].isInfixAs():
let exvar = t[i][j][2] # ex1 in `except ExceptType as ex1:`
fillLocalName(p, exvar.sym)
backendEnsureMutable exvar.sym
fillLoc(exvar.sym.locImpl, locTemp, exvar, OnUnknown)
startBlockWith(p):
lineCg(p, cpsStmts, "catch ($1& $2) {$n", [getTypeDesc(p.module, t[i][j][1].typ), rdLoc(exvar.sym.loc)])
else:
startBlockWith(p):
lineCg(p, cpsStmts, "catch ($1&) {$n", [getTypeDesc(p.module, t[i][j].typ)])
genExceptBranchBody(t[i][^1]) # exception handler body will duplicated for every type
endBlockWith(p):
p.s(cpsStmts).add("}\n")
discard pop(p.nestedTryStmts)
if t[^1].kind == nkFinally:
# c++ does not have finally, therefore code needs to be generated twice
if not catchAllPresent:
# finally requires catch all presence
startBlockWith(p):
p.s(cpsStmts).add("catch (...) {\n")
genStmts(p, t[^1].firstSon)
line(p, cpsStmts, "throw;\n")
endBlockWith(p):
p.s(cpsStmts).add("}\n")
genSimpleBlock(p, t[^1].firstSon)
proc bodyCanRaise(p: BProc; n: PNode): bool =
case n.kind
of nkCallKinds:
@@ -1904,22 +1818,6 @@ proc genDiscriminantCheck(p: BProc, a, tmp: TLoc, objtype: PType,
if p.config.exc == excGoto:
raiseExit(p)
when false:
proc genCaseObjDiscMapping(p: BProc, e: PNode, t: PType, field: PSym; d: var TLoc) =
const ObjDiscMappingProcSlot = -5
var theProc: PSym = nil
for idx, p in items(t.methods):
if idx == ObjDiscMappingProcSlot:
theProc = p
break
if theProc == nil:
theProc = genCaseObjDiscMapping(t, field, e.info, p.module.g.graph, p.module.idgen)
t.methods.add((ObjDiscMappingProcSlot, theProc))
var call = newNodeIT(nkCall, e.info, getSysType(p.module.g.graph, e.info, tyUInt8))
call.add newSymNode(theProc)
call.add e
expr(p, call, d)
proc asgnFieldDiscriminant(p: BProc, e: PNode) =
var dotExpr = e.firstSon
if dotExpr.kind == nkCheckedFieldExpr: dotExpr = dotExpr.firstSon

View File

@@ -1461,8 +1461,6 @@ proc discriminatorTableName(m: BModule; objtype: PType, d: PSym): Rope =
internalError(m.config, d.info, "anonymous obj with discriminator")
result = "NimDT_$1_$2" % [rope($hashType(objtype, m.config)), rope(d.name.s.mangle)]
proc rope(arg: Int128): Rope = rope($arg)
proc discriminatorTableDecl(m: BModule; objtype: PType, d: PSym, result: var Builder) =
cgsym(m, "TNimNode")
var tmp = discriminatorTableName(m, objtype, d)

View File

@@ -1894,10 +1894,6 @@ proc getFileHeader(conf: ConfigRef; cfile: Cfile): Rope =
addNimDefines(res, conf)
result = extract(res)
proc getSomeNameForModule(conf: ConfigRef, filename: AbsoluteFile): Rope =
## Returns a mangled module name.
result = mangleModuleName(conf, filename).mangle
proc getSomeNameForModule*(m: BModule): Rope =
## Returns a mangled module name.
assert m.module.kind == skModule
@@ -2353,7 +2349,6 @@ proc hcrGetProcLoadCode(builder: var Builder, m: BModule, sym, prefix, handle, g
assert prc != nil
fillProcLoc(m, prc.ast[namePos])
var extname = prefix & sym
var tmp = mangleDynLibProc(prc)
backendEnsureMutable prc
prc.locImpl.snippet = tmp
@@ -2835,15 +2830,6 @@ proc writeModule(m: BModule) =
code = stripCnifMarks(code)
registerModuleCode(m, cf, code)
proc updateCachedModule(m: BModule) =
let cfile = getCFile(m)
var cf = Cfile(nimname: m.module.name.s, cname: cfile,
obj: completeCfilePath(m.config, toObjFile(m.config, cfile)), flags: {})
if sfMainModule notin m.module.flags:
genMainProc(m)
cf.flags = {CfileFlag.Cached}
addFileToCompile(m.config, cf)
proc generateLibraryDestroyGlobals(graph: ModuleGraph; m: BModule; body: PNode; isDynlib: bool): PSym =
let prefixedName = m.config.nimMainPrefix & "NimDestroyGlobals"
let procname = getIdent(graph.cache, prefixedName)

View File

@@ -843,6 +843,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
of "hotcodereloading":
processOnOffSwitchG(conf, {optHotCodeReloading}, arg, pass, info)
if conf.hcrOn:
warningDeprecated(conf, info, "hotCodeReloading is deprecated, see https://github.com/nim-lang/RFCs/issues/573 for further information")
defineSymbol(conf.symbols, "hotcodereloading")
defineSymbol(conf.symbols, "useNimRtl")
# hardcoded linking with dynamic runtime for MSVC for smaller binaries

View File

@@ -269,10 +269,8 @@ proc conceptsMatch(c: PContext, fc, ac: PType; m: var MatchCon): MatchKind =
let
fn = fc.conceptBody
an = ac.conceptBody
sameLen = fc.len == ac.len
var match = false
for fdef in fn:
var cmpResult = false
for ia, ndef in an:
match = cmpConceptDefs(c, fdef, ndef, m)
if match:
@@ -330,13 +328,10 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool =
result = matchType(c, f.skipModifier, a, m)
of tyTypeDesc:
if isSelf(f):
let ua = a.skipTypes(asymmetricConceptParamMods)
if m.magic in {mArrPut, mArrGet}:
if m.potentialImplementation.reduceToBase.kind in arrPutGetMagicApplies:
bindParam(c, m, a, last m.potentialImplementation)
result = true
#elif ua.isConcept:
# result = matchType(c, m.concpt, ua, m)
else:
result = matchType(c, a.skipTypes(ignorableForArgType), m.potentialImplementation, m)
else:

View File

@@ -425,12 +425,6 @@ template dispA(conf: ConfigRef; dest: var string, xml, tex: string,
if not conf.isLatexCmd: dest.addf(xml, args)
else: dest.addf(tex, args)
proc getVarIdx(varnames: openArray[string], id: string): int =
for i in 0..high(varnames):
if cmpIgnoreStyle(varnames[i], id) == 0:
return i
result = -1
proc genComment(d: PDoc, n: PNode): PRstNode =
if n.comment.len > 0:
if optDocRaw in d.conf.globalOptions:

View File

@@ -29,7 +29,6 @@ proc shouldProcess(g: PGen): bool =
template closeImpl(body: untyped) {.dirty.} =
var g = PGen(p)
let useWarning = sfMainModule notin g.module.flags
let groupedToc = true
if shouldProcess(g):
finishGenerateDoc(g.doc)
body
@@ -41,7 +40,7 @@ template closeImpl(body: untyped) {.dirty.} =
proc closeDoc*(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
result = nil
closeImpl:
writeOutput(g.doc, useWarning, groupedToc)
writeOutput(g.doc, useWarning, true)
proc closeJson*(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
result = nil

View File

@@ -49,65 +49,3 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGener
result.ast = n
incl result.flagsImpl, {sfFromGeneric, sfNeverRaises}
setHookDisamb(g, result, "$enumtostr", t)
proc searchObjCaseImpl(obj: PNode; field: PSym): PNode =
case obj.kind
of nkSym:
result = nil
of nkElse, nkOfBranch:
result = searchObjCaseImpl(obj.lastSon, field)
else:
if obj.kind == nkRecCase and obj[0].kind == nkSym and obj[0].sym == field:
result = obj
else:
result = nil
for x in obj:
result = searchObjCaseImpl(x, field)
if result != nil: break
proc searchObjCase(t: PType; field: PSym): PNode =
result = searchObjCaseImpl(t.n, field)
if result == nil and t.baseClass != nil:
result = searchObjCase(t.baseClass.skipTypes({tyAlias, tyGenericInst, tyRef, tyPtr}), field)
doAssert result != nil
proc genCaseObjDiscMapping*(t: PType; field: PSym; info: TLineInfo; g: ModuleGraph; idgen: IdGenerator): PSym =
result = newSym(skProc, getIdent(g.cache, "objDiscMapping"), idgen, t.owner, info)
let dest = newSym(skParam, getIdent(g.cache, "e"), idgen, result, info)
dest.typ = field.typ
let res = newSym(skResult, getIdent(g.cache, "result"), idgen, result, info)
res.typ = getSysType(g, info, tyUInt8)
result.typ = newType(tyProc, idgen, t.owner)
result.typ.n = newNodeI(nkFormalParams, info)
rawAddSon(result.typ, res.typ)
result.typ.n.add newNodeI(nkEffectList, info)
result.typ.addParam dest
var body = newNodeI(nkStmtList, info)
var caseStmt = newNodeI(nkCaseStmt, info)
caseStmt.add(newSymNode dest)
let subObj = searchObjCase(t, field)
for i in 1..<subObj.len:
let ofBranch = subObj[i]
var newBranch = newNodeI(ofBranch.kind, ofBranch.info)
for j in 0..<ofBranch.len-1:
newBranch.add ofBranch[j]
newBranch.add newTree(nkStmtList, newTree(nkFastAsgn, newSymNode(res), newIntNode(nkInt8Lit, i)))
caseStmt.add newBranch
body.add(caseStmt)
var n = newNodeI(nkProcDef, info, bodyPos+2)
for i in 0..<n.len: n[i] = newNodeI(nkEmpty, info)
n[namePos] = newSymNode(result)
n[paramsPos] = result.typ.n
n[bodyPos] = body
n[resultPos] = newSymNode(res)
result.ast = n
incl result.flagsImpl, {sfFromGeneric, sfNeverRaises}

View File

@@ -173,7 +173,6 @@ template hasDestructorOrAsgn(c: var Con, typ: PType): bool =
proc isLastRead(n: PNode; c: var Con; s: var Scope): bool =
if not hasDestructorOrAsgn(c, n.typ): return true
let m = skipConvDfa(n)
result = isLastReadImpl(n, c, s)
proc isFirstWrite(n: PNode; c: var Con): bool =

View File

@@ -340,9 +340,6 @@ proc `*`*(a: Int128, b: int32): Int128 =
if b < 0:
result = -result
proc `*=`(a: var Int128, b: int32) =
a = a * b
proc makeInt128(high, low: uint64): Int128 =
result = Zero
result.udata[0] = cast[uint32](low)

View File

@@ -148,11 +148,6 @@ proc newGlobals(): PGlobals =
typeInfoGenerated: initIntSet()
)
proc initCompRes(): TCompRes =
result = TCompRes(address: "", res: "",
tmpLoc: "", typ: etyNone, kind: resNone
)
proc rdLoc(a: TCompRes): Rope {.inline.} =
if a.typ != etyBaseIndex:
result = a.res
@@ -594,15 +589,6 @@ proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string,
r.res = "(($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer]
r.kind = resExpr
template ternaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) =
var x, y, z: TCompRes
useMagic(p, magic)
gen(p, n[1], x)
gen(p, n[2], y)
gen(p, n[3], z)
r.res = frmt % [x.rdLoc, y.rdLoc, z.rdLoc]
r.kind = resExpr
template unaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) =
# $1 binds to n[1], if $2 is present it will be substituted to a tmp of $1
useMagic(p, magic)
@@ -1182,7 +1168,6 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode; isAsmStmt = false) =
of nkStrLit..nkTripleStrLit:
p.body.add(it.strVal)
of nkSym:
let v = it.sym
# for backwards compatibility we don't deref syms here :-(
if false:
discard
@@ -1255,17 +1240,6 @@ proc generateHeader(p: PProc, prc: PSym): Rope =
result.add(name)
result.add("_Idx")
proc countJsParams(typ: PType): int =
result = 0
for i in 1..<typ.n.len:
assert(typ.n[i].kind == nkSym)
var param = typ.n[i].sym
if isCompileTimeOnly(param.typ): continue
if mapType(param.typ) == etyBaseIndex:
inc result, 2
else:
inc result
const
nodeKindsNeedNoCopy = {nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit,
nkFloatLit..nkFloat64Lit, nkPar, nkStringToCString,
@@ -1797,12 +1771,6 @@ proc genArgs(p: PProc, n: PNode, r: var TCompRes; start=1) =
inc emitted
hasArgs = true
r.res.add(")")
when false:
# XXX look into this:
let jsp = countJsParams(typ)
if emitted != jsp and tfVarargs notin typ.flags:
localError(p.config, n.info, "wrong number of parameters emitted; expected: " & $jsp &
" but got: " & $emitted)
r.kind = resExpr
proc genOtherArg(p: PProc; n: PNode; i: int; typ: PType;
@@ -2335,9 +2303,6 @@ proc genJSArrayConstr(p: PProc, n: PNode, r: var TCompRes) =
r.res.add("]")
proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
var
a: TCompRes
line, filen: Rope
var op = n[0].sym.magic
case op
of mOr: genOr(p, n[1], n[2], r)
@@ -2594,7 +2559,6 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) =
r.kind = resExpr
var initList : Rope = ""
var fieldIDs = initIntSet()
let nTyp = n.typ.skipTypes(abstractInst)
for i in 1..<n.len:
if i > 1: initList.add(", ")
var it = n[i]

View File

@@ -126,11 +126,6 @@ const
paramName* = ":envP"
envName* = ":env"
proc newCall(a: PSym, b: PNode): PNode =
result = newNodeI(nkCall, a.info)
result.add newSymNode(a)
result.add b
proc createClosureIterStateType*(g: ModuleGraph; iter: PSym; idgen: IdGenerator): PType =
var n = newNodeI(nkRange, iter.info)
n.add newIntNode(nkIntLit, -1)
@@ -288,7 +283,6 @@ proc liftIterSym*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: PSym): PN
addVar(v, env)
result.add(v)
# add 'new' statement:
#result.add newCall(getSysSym(g, n.info, "internalNew"), env)
result.add genCreateEnv(env)
createTypeBoundOpsLL(g, env.typ, n.info, idgen, owner)
result.add makeClosure(g, idgen, iter, env, n.info)

View File

@@ -735,17 +735,11 @@ proc getEscapedChar(L: var Lexer, tok: var Token) =
else: lexMessage(L, errGenerated, "invalid character constant")
proc handleCRLF(L: var Lexer, pos: int): int =
template registerLine =
let col = L.getColNumber(pos)
case L.buf[pos]
of CR:
registerLine()
result = nimlexbase.handleCR(L, pos)
of LF:
registerLine()
result = nimlexbase.handleLF(L, pos)
else: result = pos
result =
case L.buf[pos]
of CR: nimlexbase.handleCR(L, pos)
of LF: nimlexbase.handleLF(L, pos)
else: pos
type
StringMode = enum

View File

@@ -596,12 +596,6 @@ proc newSeqCall(c: var TLiftCtx; x, y: PNode): PNode =
lenCall.typ = getSysType(c.g, x.info, tyInt)
result.add lenCall
proc setLenStrCall(c: var TLiftCtx; x, y: PNode): PNode =
let lenCall = genBuiltin(c, mLengthStr, "len", y)
lenCall.typ = getSysType(c.g, x.info, tyInt)
result = genBuiltin(c, mSetLengthStr, "setLen", x) # genAddr(g, x))
result.add lenCall
proc setLenSeqCall(c: var TLiftCtx; t: PType; x, y: PNode; noinit = false): PNode =
let lenCall = genBuiltin(c, mLengthSeq, "len", y)
lenCall.typ = getSysType(c.g, x.info, tyInt)

View File

@@ -209,22 +209,6 @@ proc commandInteractive(graph: ModuleGraph) =
let s = llStreamOpenStdIn(onPrompt = proc() = flushDot(graph.config))
discard processPipelineModule(graph, m, idgen, s)
proc commandScan(cache: IdentCache, config: ConfigRef) =
var f = addFileExt(AbsoluteFile mainCommandArg(config), NimExt)
var stream = llStreamOpen(f, fmRead)
if stream != nil:
var
L: Lexer = default(Lexer)
tok: Token = default(Token)
openLexer(L, f, stream, cache, config)
while true:
rawGetTok(L, tok)
printTok(config, tok)
if tok.tokType == tkEof: break
closeLexer(L)
else:
rawMessage(config, errGenerated, "cannot open file: " & f.string)
const
PrintRopeCacheStats = false

View File

@@ -432,10 +432,6 @@ proc addDispatchers*(g: ModuleGraph, value: PSym) =
# TODO: add it for packed modules
g.dispatchers.add value
iterator resolveLazySymSeq(g: ModuleGraph, list: var seq[PSym]): PSym =
for it in list.mitems:
yield it
proc setMethodsPerType*(g: ModuleGraph; id: ItemId, methods: seq[PSym]) =
# TODO: add it for packed modules
g.methodsPerType[id] = methods
@@ -668,10 +664,6 @@ proc hash*(u: SigHash): Hash =
proc hash*(x: FileIndex): Hash {.borrow.}
template getPContext(): untyped =
when c is PContext: c
else: c.c
when defined(nimsuggest):
template onUse*(info: TLineInfo; s: PSym; isGenericInstance = false) = discard
template onDefResolveForward*(info: TLineInfo; s: PSym) = discard

View File

@@ -24,10 +24,6 @@ template instLoc*(): InstantiationInfo = instantiationInfo(-2, fullPaths = true)
template toStdOrrKind(stdOrr): untyped =
if stdOrr == stdout: stdOrrStdout else: stdOrrStderr
proc toLowerAscii(a: var string) {.inline.} =
for c in mitems(a):
if isUpperAscii(c): c = char(uint8(c) xor 0b0010_0000'u8)
proc flushDot*(conf: ConfigRef) =
## safe to call multiple times
let stdOrr = if optStdout in conf.globalOptions: stdout else: stderr
@@ -83,7 +79,8 @@ proc canonicalCase(path: var string) {.inline.} =
## the idea is to only use this for checking whether a path is already in
## the table but otherwise keep the original case
when FileSystemCaseSensitive: discard
else: toLowerAscii(path)
else:
for c in mitems(path): c = toLowerAscii(c)
proc fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool =
var

View File

@@ -27,7 +27,6 @@ proc getPackage*(conf: ConfigRef; cache: IdentCache; fileIdx: FileIndex): PSym =
## * `modulegraphs.getPackage`
let
filename = AbsoluteFile toFullPath(conf, fileIdx)
name = getIdent(cache, splitFile(filename).name)
info = newLineInfo(fileIdx, 1, 1)
pkgName = getPackageName(conf, filename.string)
pkgIdent = getIdent(cache, pkgName)

View File

@@ -1,4 +1,3 @@
import std/intsets
import ast, options, lineinfos, pathutils, msgs, modulegraphs, packages
proc skipCodegen*(config: ConfigRef; n: PNode): bool {.inline.} =

View File

@@ -11,25 +11,10 @@
# This is needed for proper handling of forward declarations.
import
ast, astalgo, msgs, semdata, types, trees, lookups
ast, astalgo, msgs, semdata, types, lookups
import std/strutils
proc equalGenericParams(procA, procB: PNode): bool =
if procA.len != procB.len: return false
for i in 0..<procA.len:
if procA[i].kind != nkSym:
return false
if procB[i].kind != nkSym:
return false
let a = procA[i].sym
let b = procB[i].sym
if a.name.id != b.name.id or
not sameTypeOrNil(a.typ, b.typ, {ExactTypeDescValues}): return
if a.ast != nil and b.ast != nil:
if not exprStructuralEquivalent(a.ast, b.ast): return
result = true
proc searchForProcAux(c: PContext, scope: PScope, fn: PSym): PSym =
const flags = {ExactGenericParams, ExactTypeDescValues,
ExactConstraints, IgnoreCC}
@@ -59,30 +44,3 @@ proc searchForProc*(c: PContext, scope: PScope, fn: PSym): tuple[proto: PSym, co
scope = scope.parent
result.proto = searchForProcAux(c, scope, fn)
result.comesFromShadowScope = true
when false:
proc paramsFitBorrow(child, parent: PNode): bool =
result = false
if child.len == parent.len:
for i in 1..<child.len:
var m = child[i].sym
var n = parent[i].sym
assert((m.kind == skParam) and (n.kind == skParam))
if not compareTypes(m.typ, n.typ, dcEqOrDistinctOf): return
if not compareTypes(child[0].typ, parent[0].typ,
dcEqOrDistinctOf): return
result = true
proc searchForBorrowProc*(c: PContext, startScope: PScope, fn: PSym): PSym =
# Searches for the fn in the symbol table. If the parameter lists are suitable
# for borrowing the sym in the symbol table is returned, else nil.
var it: TIdentIter = default(TIdentIter)
for scope in walkScopes(startScope):
result = initIdentIter(it, scope.symbols, fn.Name)
while result != nil:
# watchout! result must not be the same as fn!
if (result.Kind == fn.kind) and (result.id != fn.id):
if equalGenericParams(result.ast[genericParamsPos],
fn.ast[genericParamsPos]):
if paramsFitBorrow(fn.typ.n, result.typ.n): return
result = NextIdentIter(it, scope.symbols)

View File

@@ -537,10 +537,6 @@ proc putNL(g: var TSrcGen, indent: int) =
g.lineLen = indent
g.pendingWhitespace = -1
proc previousNL(g: TSrcGen): bool =
result = g.pendingNL >= 0 or (g.tokens.len > 0 and
g.tokens[^1].kind == tkSpaces)
proc putNL(g: var TSrcGen) =
putNL(g, g.indent)
@@ -646,28 +642,6 @@ proc maxLineLength(s: string): int =
inc(lineLen)
inc(i)
proc putRawStr(g: var TSrcGen, kind: TokType, s: string) =
var i = 0
let hi = s.len - 1
var str = ""
while i <= hi:
case s[i]
of '\r':
put(g, kind, str)
str = ""
inc(i)
if i <= hi and s[i] == '\n': inc(i)
optNL(g, 0)
of '\n':
put(g, kind, str)
str = ""
inc(i)
optNL(g, 0)
else:
str.add(s[i])
inc(i)
put(g, kind, str)
proc containsNL(s: string): bool =
for i in 0..<s.len:
case s[i]
@@ -1784,7 +1758,6 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) =
gsub(g, n, 1)
of nkInfix:
if n.len < 3:
var i = 0
put(g, tkOpr, "Too few children for nkInfix")
return
let oldLineLen = g.lineLen # we cache this because lineLen gets updated below
@@ -2076,7 +2049,6 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) =
of nkPragma:
if g.inPragma <= 0:
inc g.inPragma
#if not previousNL(g):
put(g, tkSpaces, Space)
put(g, tkCurlyDotLe, "{.")
gcomma(g, n, emptyContext)

View File

@@ -34,14 +34,6 @@ when defined(windows) and defined(bcc):
#endif
""".}
proc c_snprintf(s: cstring; n: uint; frmt: cstring): cint {.importc: "snprintf", header: "<stdio.h>", nodecl, varargs.}
when not declared(signbit):
proc c_signbit(x: SomeFloat): cint {.importc: "signbit", header: "<math.h>".}
proc signbit*(x: SomeFloat): bool {.inline.} =
result = c_signbit(x) != 0
import std/formatfloat
proc toStrMaxPrecision*(f: BiggestFloat | float32): string =

View File

@@ -332,7 +332,6 @@ proc typeAllowedCheck(c: PContext; info: TLineInfo; typ: PType; kind: TSymKind;
proc paramsTypeCheck(c: PContext, typ: PType) {.inline.} =
typeAllowedCheck(c, typ.n.info, typ, skProc)
proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym
proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode
proc semWhen(c: PContext, n: PNode, semCheck: bool = true): PNode
proc semTemplateExpr(c: PContext, n: PNode, s: PSym,

View File

@@ -689,7 +689,7 @@ proc bracketNotFoundError(c: PContext; n: PNode; flags: TExprFlags) =
baseFilter + {skIterator}
else: baseFilter
# this will add the errors:
var r = resolveOverloads(c, n, n, filter, flags, errors, true)
discard resolveOverloads(c, n, n, filter, flags, errors, true)
if errors.len == 0:
localError(c.config, n.info, "could not resolve: " & $n)
else:
@@ -926,15 +926,6 @@ proc semResolvedCall(c: PContext, x: var TCandidate,
result.typ = finalCallee.typ.returnType
updateDefaultParams(c, result)
proc canDeref(n: PNode): bool {.inline.} =
result = n.len >= 2 and (let t = n[1].typ;
t != nil and t.skipTypes({tyGenericInst, tyAlias, tySink}).kind in {tyPtr, tyRef})
proc tryDeref(n: PNode): PNode =
result = newNodeI(nkHiddenDeref, n.info)
result.typ = n.typ.skipTypes(abstractInst)[0]
result.add n
proc semOverloadedCall(c: PContext, n, nOrig: PNode,
filter: TSymKinds, flags: TExprFlags;
expectedType: PType = nil): PNode =

View File

@@ -186,6 +186,12 @@ type
forwardFieldUpdates*: seq[(PType, PNode, PType)]
# object/tuple field definitions whose default values mention forward
# types and need delayed const checking
forwardFlagUpdates*: seq[(PType, PType)]
# (owner, son) pairs whose `propagateToOwner` ran on a not yet reified
# forward type and has to be redone in the final pass
staleTypeFlags*: IntSet
# ids of the owners in `forwardFlagUpdates`; their flags are provisional
# too, so reading them makes the reader provisional in turn
inTypeofContext*: int
semAsgnOpr*: proc (c: PContext; n: PNode; k: TNodeKind): PNode {.nimcall.}
@@ -369,6 +375,7 @@ proc newContext*(graph: ModuleGraph; module: PSym): PContext =
unknownIdents: initIntSet(),
shadowDiscardedDefs: initIntSet(),
realizedDefs: initIntSet(),
staleTypeFlags: initIntSet(),
cache: graph.cache,
graph: graph,
signatures: initStrTable(),

View File

@@ -22,7 +22,6 @@ const
errNamedExprExpected = "named expression expected"
errNamedExprNotAllowed = "named expression not allowed here"
errFieldInitTwice = "field initialized twice: '$1'"
errUndeclaredFieldX = "undeclared field: '$1'"
proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
flags: TExprFlags = {}; expectedType: PType = nil): PNode =
@@ -770,17 +769,6 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
n.typ = newType
proc arrayConstrType(c: PContext, n: PNode): PType =
var typ = newTypeS(tyArray, c)
rawAddSon(typ, nil) # index type
if n.len == 0:
rawAddSon(typ, newTypeS(tyEmpty, c)) # needs an empty basetype!
else:
var t = skipTypes(n[0].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink})
addSonSkipIntLit(typ, t, c.idgen)
typ.setIndexType makeRangeType(c, 0, n.len - 1, n.info)
result = typ
proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode =
result = newNodeI(nkBracket, n.info)
# nkBracket nodes can also be produced by the VM as seq constant nodes
@@ -1339,7 +1327,6 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent,
else: illFormedAst(n, c.config)
const
tyTypeParamsHolders = {tyGenericInst, tyCompositeTypeClass}
tyDotOpTransparent = {tyVar, tyLent, tyPtr, tyRef, tyOwned, tyAlias, tySink}
proc readTypeParameter(c: PContext, typ: PType,
@@ -2326,24 +2313,6 @@ proc semDeclared(c: PContext, n: PNode, onlyCurrentScope: bool): PNode =
result.info = n.info
result.typ = getSysType(c.graph, n.info, tyBool)
proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym =
## The argument to the proc should be nkCall(...) or similar
## Returns the macro/template symbol
if isCallExpr(n):
var expandedSym = qualifiedLookUp(c, n[0], {checkUndeclared})
if expandedSym == nil:
errorUndeclaredIdentifier(c, n.info, n[0].renderTree)
return errorSym(c, n[0])
if expandedSym.kind notin {skMacro, skTemplate}:
localError(c.config, n.info, "'$1' is not a macro or template" % expandedSym.name.s)
return errorSym(c, n[0])
result = expandedSym
else:
localError(c.config, n.info, "'$1' is not a macro or template" % n.renderTree)
result = errorSym(c, n)
proc expectString(c: PContext, n: PNode): string =
var n = semConstExpr(c, n)
if n.kind in nkStrKinds:
@@ -2358,14 +2327,6 @@ proc newAnonSym(c: PContext; kind: TSymKind, info: TLineInfo): PSym =
proc semExpandToAst(c: PContext, n: PNode): PNode =
let macroCall = n[1]
when false:
let expandedSym = expectMacroOrTemplateCall(c, macroCall)
if expandedSym.kind == skError: return n
macroCall[0] = newSymNode(expandedSym, macroCall.info)
markUsed(c, n.info, expandedSym)
onUse(n.info, expandedSym)
if isCallExpr(macroCall):
for i in 1..<macroCall.len:
#if macroCall[0].typ[i].kind != tyUntyped:
@@ -2537,7 +2498,6 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
let oldInStaticContext = c.inStaticContext
let oldProcCon = c.p
c.generics = @[]
var err: string
try:
result = semExpr(c, n, flags)
if result != nil and efNoSem2Check notin flags:

View File

@@ -19,7 +19,7 @@ import std/[strutils, math, strtabs]
#from system/memory import nimCStrLen
when defined(nimPreviewSlimSystem):
import std/[assertions, formatfloat]
import std/[assertions]
proc errorType*(g: ModuleGraph): PType =
## creates a type representing an error state
@@ -121,21 +121,6 @@ proc ordinalValToString*(a: PNode; g: ModuleGraph): string =
else:
result = $x
proc isFloatRange(t: PType): bool {.inline.} =
result = t.kind == tyRange and t.elementType.kind in {tyFloat..tyFloat128}
proc isIntRange(t: PType): bool {.inline.} =
result = t.kind == tyRange and t.elementType.kind in {
tyInt..tyInt64, tyUInt8..tyUInt32}
proc pickIntRange(a, b: PType): PType =
if isIntRange(a): result = a
elif isIntRange(b): result = b
else: result = a
proc isIntRangeOrLit(t: PType): bool =
result = isIntRange(t) or isIntLit(t)
proc evalOp(m: TMagic, n, a, b, c: PNode; idgen: IdGenerator; g: ModuleGraph): PNode =
# b and c may be nil
result = nil
@@ -392,11 +377,6 @@ proc rangeCheck(n: PNode, value: Int128; g: ModuleGraph) =
localError(g.config, n.info, "cannot convert " & $value &
" to " & typeToString(n.typ))
proc floatRangeCheck(n: PNode, value: BiggestFloat; g: ModuleGraph) =
if value < firstFloat(n.typ) or value > lastFloat(n.typ):
localError(g.config, n.info, "cannot convert " & $value &
" to " & typeToString(n.typ))
proc foldConv(n, a: PNode; idgen: IdGenerator; g: ModuleGraph; check = false): PNode =
let dstTyp = skipTypes(n.typ, abstractRange - {tyTypeDesc})
let srcTyp = skipTypes(a.typ, abstractRange - {tyTypeDesc})

View File

@@ -233,7 +233,7 @@ proc fuzzyLookup(c: PContext, n: PNode, flags: TSemGenericFlags,
if s.kind == skType: # don't put types in sym choice
var ambig = false
if candidates.len > 1:
let s2 = searchInScopes(c, ident, ambig)
discard searchInScopes(c, ident, ambig)
result = newDot(result, semGenericStmtSymbol(c, n, s, ctx, flags,
isAmbiguous = ambig, fromDotExpr = true))
else:

View File

@@ -440,7 +440,7 @@ proc initConstrContext(t: PType, initExpr: PNode): ObjConstrContext =
proc computeRequiresInit(c: PContext, t: PType): bool =
assert t.kind == tyObject
var constrCtx = initConstrContext(t, newNode(nkObjConstr))
let initResult = semConstructTypeAux(c, constrCtx, {efWantNoDefaults})
discard semConstructTypeAux(c, constrCtx, {efWantNoDefaults})
constrCtx.missingFields.len > 0
proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) =
@@ -450,7 +450,7 @@ proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) =
assert objType != nil
if objType.kind == tyObject:
var constrCtx = initConstrContext(objType, newNodeI(nkObjConstr, info))
let initResult = semConstructTypeAux(c, constrCtx, {efIgnoreDefaults})
discard semConstructTypeAux(c, constrCtx, {efIgnoreDefaults})
if constrCtx.missingFields.len > 0:
localError(c.config, info,
"The $1 type doesn't have a default value. The following fields must be initialized: $2." % [typeToString(t), listSymbolNames(constrCtx.missingFields)])

View File

@@ -1006,7 +1006,6 @@ proc trackIf(tracked: PEffects, n: PNode) =
proc trackBlock(tracked: PEffects, n: PNode; typ: PType) =
if n.kind in {nkStmtList, nkStmtListExpr}:
let myBlock = tracked.currentBlock
var oldState = -1
for i in 0..<n.len:
if hasSubnodeWith(n[i], nkBreakStmt):
@@ -1029,11 +1028,6 @@ proc trackBlock(tracked: PEffects, n: PNode; typ: PType) =
else:
track(tracked, n)
proc cstringCheck(tracked: PEffects; n: PNode) =
if n[0].typ.kind == tyCstring and (let a = skipConv(n[1]);
a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}):
message(tracked.config, n.info, warnUnsafeCode, renderTree(n))
proc patchResult(c: PEffects; n: PNode) =
if n.kind == nkSym and n.sym.kind == skResult:
let fn = c.owner
@@ -1511,7 +1505,6 @@ proc track(tracked: PEffects, n: PNode) =
dec tracked.leftPartOfAsgn
addAsgnFact(tracked.guards, n[0], n[1])
notNilCheck(tracked, n[1], n[0].typ)
when false: cstringCheck(tracked, n)
if tracked.owner.kind != skMacro and n[0].typ.kind notin {tyOpenArray, tyVarargs}:
createTypeBoundOps(tracked, n[0].typ, n.info)
if n[0].kind != nkSym or not isLocalSym(tracked, n[0].sym):
@@ -2001,7 +1994,6 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
patchResult(t, ensuresSpec)
effects[ensuresEffects] = ensuresSpec
var mutationInfo = MutationInfo()
if views in c.features:
var partitions = computeGraphPartitions(s, body, g, {borrowChecking})
checkBorrowedLocations(partitions, body, g.config)

View File

@@ -17,18 +17,14 @@ const
errInvalidControlFlowX = "invalid control flow: $1"
errSelectorMustBeOfCertainTypes = "selector must be of an ordinal type, float or string"
errExprCannotBeRaised = "only a 'ref object' can be raised"
errBreakOnlyInLoop = "'break' only allowed in loop construct"
errExceptionAlreadyHandled = "exception already handled"
errYieldNotAllowedHere = "'yield' only allowed in an iterator"
errYieldNotAllowedInTryStmt = "'yield' cannot be used within 'try' in a non-inlined iterator"
errInvalidNumberOfYieldExpr = "invalid number of 'yield' expressions"
errCannotReturnExpr = "current routine cannot return an expression"
errGenericLambdaNotAllowed = "A nested proc can have generic parameters only when " &
"it is used as an operand to another routine and the types " &
"of the generic paramers can be inferred from the expected signature."
errCannotInferTypeOfTheLiteral = "cannot infer the type of the $1"
errCannotInferReturnType = "cannot infer the return type of '$1'"
errCannotInferStaticParam = "cannot infer the value of the static param '$1'"
errProcHasNoConcreteType = "'$1' doesn't have a concrete type, due to unspecified generic parameters."
errLetNeedsInit = "'let' symbol requires an initialization"
errThreadvarCannotInit = "a thread var cannot be initialized explicitly; this would only run for the main thread"
@@ -545,7 +541,6 @@ proc semUsing(c: PContext; n: PNode): PNode =
strTableIncl(c.signatures, v)
else:
localError(c.config, a.info, "'using' section must have a type")
var def: PNode
if a[^1].kind != nkEmpty:
localError(c.config, a.info, "'using' sections cannot contain assignments")
@@ -1837,6 +1832,24 @@ proc typeSectionFinalPass(c: PContext, n: PNode) =
for (owner, field, expectedType) in c.forwardFieldUpdates:
semDelayedFieldDefault(c, owner, expectedType, field)
c.forwardFieldUpdates = @[]
# a son that still was a `tyForward` could not propagate `tfHasAsgn` and
# friends to its owner back then, see `rememberFlagUpdate`. Now that every
# forward declaration has a body, redo those propagations. They are recorded
# in declaration order rather than dependency order and an owner can itself
# be the son of another pair, so repeat until nothing changes; this
# terminates because flags are only ever added.
if c.forwardFlagUpdates.len > 0:
let updates = move c.forwardFlagUpdates
c.staleTypeFlags = initIntSet()
var changed = true
while changed:
changed = false
for (owner, elem) in updates:
let before = owner.flags
propagateToOwner(owner, elem)
if owner.flags != before: changed = true
for i in 0..<n.len:
var a = n[i]
if a.kind == nkCommentStmt: continue

View File

@@ -581,7 +581,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
result.add newIdentNode(getIdent(c.c.cache, "[]="), n.info)
for i in 0..<a.len: result.add(a[i])
result.add(b)
let a0 = semTemplBody(c, a[0])
discard semTemplBody(c, a[0])
result = semTemplBodySons(c, result)
of nkCurlyExpr:
if a.typ == nil:

View File

@@ -19,13 +19,11 @@ const
errOverflowInEnumX = "The enum '$1' exceeds its maximum value ($2)"
errOrdinalTypeExpected = "ordinal type expected; given: $1"
errSetTooBig = "set is too large; use `std/sets` for ordinal types with more than 2^16 elements"
errBaseTypeMustBeOrdinal = "base type of a set must be an ordinal"
errInheritanceOnlyWithNonFinalObjects = "inheritance only works with non-final objects"
errXExpectsOneTypeParam = "'$1' expects one type parameter"
errArrayExpectsTwoTypeParams = "array expects two type parameters"
errInvalidVisibilityX = "invalid visibility: '$1'"
errXCannotBeAssignedTo = "'$1' cannot be assigned to"
errIteratorNotAllowed = "iterators can only be defined at the module's top level"
errXNeedsReturnType = "$1 needs a return type"
errNoReturnTypeDeclared = "no return type declared"
errTIsNotAConcreteType = "'$1' is not a concrete type"
@@ -60,6 +58,18 @@ proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext): PType =
else:
result = newTypeS(kind, c)
proc rememberFlagUpdate(c: PContext; owner, elem: PType) =
## `propagateToOwner` just derived `owner`'s `tfHasAsgn` & friends from
## `elem`, but inside a type section `elem` can still be an unreified
## `tyForward` which has nothing to derive from yet -- and a type that read
## such a type is provisional in turn. Remember the pair so
## `typeSectionFinalPass` can redo the propagation once every forward
## declaration has a body, the same way `forwardFieldUpdates` defers the
## field defaults.
if elem != nil and (elem.kind == tyForward or elem.id in c.staleTypeFlags):
c.forwardFlagUpdates.add (owner, elem)
c.staleTypeFlags.incl owner.id
proc newConstraint(c: PContext, k: TTypeKind): PType =
result = newTypeS(tyBuiltInTypeClass, c)
result.incl tfCheckedForDestructor
@@ -221,6 +231,7 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType =
var base = semTypeNode(c, n[1], nil)
if base.kind == tyTypeDesc: base = base.base # unwrap from type traits like distinctBase
addSonSkipIntLit(result, base, c.idgen)
rememberFlagUpdate(c, result, base)
if base.kind in {tyGenericInst, tyAlias, tySink}: base = skipModifier(base)
if base.kind notin {tyGenericParam, tyGenericInvocation, tyFromExpr}:
if base.kind == tyForward:
@@ -239,6 +250,7 @@ proc semContainerArg(c: PContext; n: PNode, kindStr: string; result: PType) =
if base.kind == tyVoid:
localError(c.config, n.info, errTIsNotAConcreteType % typeToString(base))
addSonSkipIntLit(result, base, c.idgen)
rememberFlagUpdate(c, result, base)
else:
localError(c.config, n.info, errXExpectsOneTypeParam % kindStr)
addSonSkipIntLit(result, errorType(c), c.idgen)
@@ -387,6 +399,7 @@ proc addSonSkipIntLitChecked(c: PContext; father, son: PType; it: PNode, id: IdG
localError(c.config, it.info, "illegal recursion in type '" & typeToString(s) & "'")
else:
propagateToOwner(father, s)
rememberFlagUpdate(c, father, s)
proc semDistinct(c: PContext, n: PNode, prev: PType): PType =
if n.len == 0: return newConstraint(c, tyDistinct)
@@ -560,6 +573,7 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType =
# index type:
result = newOrPrevType(tyArray, prev, c, indx)
addSonSkipIntLit(result, base, c.idgen)
rememberFlagUpdate(c, result, base)
else:
localError(c.config, n.info, errArrayExpectsTwoTypeParams)
result = newOrPrevType(tyError, prev, c)
@@ -636,6 +650,7 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
fSym.sym.ast.flags.incl nfSkipFieldChecking
result.n.add fSym
addSonSkipIntLit(result, typ, c.idgen)
rememberFlagUpdate(c, result, typ)
styleCheckDef(c, a[j].info, field)
onDef(field.info, field)
if result.n.len == 0: result.n = nil
@@ -989,6 +1004,7 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
n[^1] = firstRange(c.config, typ)
hasDefaultField = true
propagateToOwner(rectype, typ)
rememberFlagUpdate(c, rectype, typ)
var fieldOwner = if c.inGenericContext > 0: c.getCurrOwner
else: rectype.sym
for i in 0..<n.len-2:
@@ -1124,6 +1140,7 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType
# the entire object needs to be checked again
c.forwardTypeUpdates.add (getCurrOwner(c), result, n) # we retry in the final pass
rawAddSon(result, realBase)
rememberFlagUpdate(c, result, realBase)
if realBase == nil and tfInheritable in flags:
result.incl tfInheritable
if tfAcyclic in flags: result.incl tfAcyclic
@@ -1947,7 +1964,6 @@ proc semTypeClass(c: PContext, n: PNode, prev: PType): PType =
return result
let
pragmas = n[1]
inherited = n[2]
var owner = getCurrOwner(c)

View File

@@ -180,35 +180,6 @@ proc prepareNode*(cl: var TReplTypeVars, n: PNode): PNode =
for i in 0..<n.safeLen:
result.add(prepareNode(cl, n[i]))
proc isTypeParam(n: PNode): bool =
# XXX: generic params should use skGenericParam instead of skType
return n.kind == nkSym and
(n.sym.kind == skGenericParam or
(n.sym.kind == skType and sfFromGeneric in n.sym.flags))
when false: # old workaround
proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode =
# This is needed for tuninstantiatedgenericcalls
# It's possible that a generic param will be used in a proc call to a
# typedesc accepting proc. After generic param substitution, such procs
# should be optionally instantiated with the correct type. In order to
# perform this instantiation, we need to re-run the generateInstance path
# in the compiler, but it's quite complicated to do so at the moment so we
# resort to a mild hack; the head symbol of the call is temporary reset and
# overload resolution is executed again (which may trigger generateInstance).
if n.kind in nkCallKinds and sfFromGeneric in n[0].sym.flags:
var needsFixing = false
for i in 1..<n.safeLen:
if isTypeParam(n[i]): needsFixing = true
if needsFixing:
n[0] = newSymNode(n[0].sym.owner)
return cl.c.semOverloadedCall(cl.c, n, n, {skProc, skFunc}, {})
for i in 0..<n.safeLen:
n[i] = reResolveCallsWithTypedescParams(cl, n[i])
return n
proc replaceObjBranches(cl: TReplTypeVars, n: PNode): PNode =
result = n
case n.kind

View File

@@ -182,7 +182,6 @@ proc matchGenericParams*(m: var TCandidate, binding: PNode, callee: PSym) =
## state is set to `csMatch` if all generic params match, `csEmpty` if
## implicit generic parameters are missing (matches but cannot instantiate),
## `csNoMatch` if a constraint fails or param count doesn't match
let c = m.c
let typeParams = callee.ast[genericParamsPos]
let paramCount = typeParams.len
let bindingCount = binding.len-1
@@ -707,8 +706,6 @@ proc recordRel(c: var TCandidate, f, a: PType, flags: TTypeRelFlags): TTypeRelat
result = isEqual
elif sameTupleLengths(a, f):
result = isEqual
let firstField = if f.kind == tyTuple: 0
else: 1
for _, ff, aa in tupleTypePairs(f, a):
var m = typeRel(c, ff, aa, flags)
if m < isSubtype: return isNone

View File

@@ -97,9 +97,7 @@ iterator tokenize*(line: string): (int, string) =
## normal JS code. This allows us to map mangled names back to Nim names.
## Yields (column, name). Doesn't yield anything but identifiers.
## See mangleName in compiler/jsgen.nim for how name mangling is done
var
col = 0
token = ""
var col = 0
while col < line.len:
var
token: string = ""
@@ -128,7 +126,6 @@ func parse*(source: string): SourceInfo =
## So it can convert those into a series of mappings
result = default(SourceInfo)
var
skipFirstLine = true
currColumn = 0
currLine = 0
currFile = ""

View File

@@ -11,7 +11,6 @@
import ast, types, idents, magicsys, msgs, options, modulegraphs,
lowerings, liftdestructors, renderer, trees
from trees import getMagic, getRoot
proc callProc(a: PNode): PNode =
result = newNodeI(nkCall, a.info)

View File

@@ -11,12 +11,12 @@
import
ast, astalgo, trees, msgs, platform, renderer, options,
lineinfos, int128, modulegraphs, astmsgs, wordrecg
lineinfos, int128, modulegraphs, astmsgs
import std/[intsets, strutils]
when defined(nimPreviewSlimSystem):
import std/[assertions, formatfloat]
import std/[assertions]
export isResolvedUserTypeClass, TPreferedDesc, typeToString
@@ -869,11 +869,6 @@ proc sameObjectTree(a, b: PNode, c: var TSameTypeClosure): bool =
else:
result = false
proc sameObjectStructures(a, b: PType, c: var TSameTypeClosure): bool =
if not sameTypeOrNilAux(a.baseClass, b.baseClass, c): return false
if not sameObjectTree(a.n, b.n, c): return false
result = true
proc sameChildrenAux(a, b: PType, c: var TSameTypeClosure): bool =
if not sameTupleLengths(a, b): return false
# XXX This is not tuple specific.

View File

@@ -158,23 +158,6 @@ proc derefPtrToReg(address: BiggestInt, typ: PType, r: var TFullReg, isAssign: b
of tyFloat64: fun(floatVal, float64, rkFloat)
else: return false
proc createStrKeepNode(x: var TFullReg; keepNode=true) =
if x.node.isNil or not keepNode:
x.node = newNode(nkStrLit)
elif x.node.kind == nkNilLit and keepNode:
when defined(useNodeIds):
let id = x.node.id
x.node[] = TNode(kind: nkStrLit)
when defined(useNodeIds):
x.node.id = id
elif x.node.kind notin {nkStrLit..nkTripleStrLit} or
nfAllConst in x.node.flags:
# XXX this is hacky; tests/txmlgen triggers it:
x.node = newNode(nkStrLit)
# It not only hackey, it is also wrong for tgentemplate. The primary
# cause of bugs like these is that the VM does not properly distinguish
# between variable definitions (var foo = e) and variable updates (foo = e).
include vmhooks
template createStr(x) =
@@ -1758,7 +1741,6 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
stackTrace(c, tos, pc, msg2)
of opcSetLenStr:
decodeB(rkNode)
#createStrKeepNode regs[ra]
regs[ra].node.strVal.setLen(regs[rb].intVal.int)
of opcOf:
decodeBC(rkInt)
@@ -2203,7 +2185,6 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
inc pc
let desttyp = c.types[c.code[pc].regBx - wordExcess]
inc pc
let srctyp = c.types[c.code[pc].regBx - wordExcess]
when hasFFI:
let dest = fficast(c.config, regs[rb].node, desttyp)

View File

@@ -874,11 +874,6 @@ proc genBinaryStmtVar(c: PCtx; n: PNode; opc: TOpcode) =
c.freeTemp(tmp)
c.freeTemp(dest)
proc genUnaryStmt(c: PCtx; n: PNode; opc: TOpcode) =
let tmp = c.genx(n[1])
c.gABC(n, opc, tmp, 0, 0)
c.freeTemp(tmp)
proc genVarargsABC(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) =
if dest < 0: dest = getTemp(c, n.typ)
var x = c.getTempRange(n.len-1, slotTempStr)

View File

@@ -48,9 +48,6 @@ import vmconv, vmmarshal
template mathop(op) {.dirty.} =
registerCallback(c, "stdlib.math." & astToStr(op), `op Wrapper`)
template osop(op) {.dirty.} =
registerCallback(c, "stdlib.os." & astToStr(op), `op Wrapper`)
template oscommonop(op) {.dirty.} =
registerCallback(c, "stdlib.oscommon." & astToStr(op), `op Wrapper`)

View File

@@ -30,7 +30,6 @@ proc dump*(conf: ConfigRef, pd: ProfileData): string =
var data = pd.data
result = "\nprof: µs #instr location"
for i in 0..<32:
var tMax: float
var infoMax: ProfileInfo = default(ProfileInfo)
var flMax: TLineInfo = default(TLineInfo)
for fl, info in data:

View File

@@ -16,7 +16,6 @@ proc dispatch(x: Base, params: ...) =
var disp = newNodeI(nkIfStmt, base.info)
let nimGetVTableSym = getCompilerProc(g, "nimGetVTable")
let ptrPNimType = nimGetVTableSym.typ.n[1].sym.typ
var nTyp = base.typ.n[1].sym.typ
var dispatchObject = newSymNode(base.typ.n[1].sym)

View File

@@ -1154,7 +1154,19 @@ template sysAssert(cond: bool, msg: string) =
cstderr.rawWrite "\n"
rawQuit 1
const hasAlloc = (hostOS != "standalone" or not defined(nogc)) and not defined(nimscript)
const
hasAlloc = (hostOS != "standalone" or not defined(nogc)) and not defined(nimscript)
hasDefaultAllocator =
hasAlloc and
not (defined(useNimRtl) or defined(useMalloc) or defined(gcRegions) or
defined(nogc) or defined(boehmgc) or defined(gogc))
hasThreadLocalAllocator =
hasDefaultAllocator and hasThreadSupport and defined(gcDestructors)
when hasThreadLocalAllocator:
# threadimpl is included before mmdisp provides these implementations.
proc initThreadAllocator() {.gcsafe, raises: [].}
proc releaseThreadAllocator() {.gcsafe, raises: [].}
when notJSnotNims and hasAlloc and not defined(nimSeqsV2):
proc addChar(s: NimString, c: char): NimString {.compilerproc, gcsafe.}
@@ -2425,6 +2437,8 @@ when notJSnotNims and hasAlloc:
{.push profiler: off.}
include "system/mmdisp"
{.pop.}
when hasThreadLocalAllocator:
initThreadAllocator()
{.push stackTrace: off, profiler: off.}
when not defined(nimSeqsV2):
include "system/sysstr"

View File

@@ -40,7 +40,7 @@ template track(op, address, size) =
#
# A deallocation of a small pointer then looks like this
#[
dealloc -> rawDealloc -> chunk.owner == addr(a) --------------> This thread owns the chunk ------> The current chunk is active -> Chunk is completely unused -----> Chunk references no foreign cells
dealloc -> rawDealloc -> chunk.owner == regionOwner(a) -------> This thread owns the chunk ------> The current chunk is active -> Chunk is completely unused -----> Chunk references no foreign cells
| | (Add cell into the current chunk) | Return the current chunk back to tlsf
| | | |
v v v v
@@ -63,6 +63,11 @@ const
# size of chunks in last matrix bin
MaxBigChunkSize = int(1'i32 shl MaxFli - 1'i32 shl (MaxFli-MaxLog2Sli-1))
HugeChunkSize = MaxBigChunkSize + 1
usesRegionHandles = hasThreadSupport and defined(gcDestructors)
# Deliberately *not* `hasThreadLocalAllocator`: this selects the chunk
# layout, which is ABI and must match between a `--useNimRtl` client and
# the RTL it links against. Whether this module owns a thread local region
# is the separate question that `hasThreadLocalAllocator` answers.
type
PTrunk = ptr Trunk
@@ -112,11 +117,15 @@ type
PChunk = ptr BaseChunk
PBigChunk = ptr BigChunk
PSmallChunk = ptr SmallChunk
SharedFreeLists = array[0..max(1, SmallChunkSize div MemAlign-1), ptr FreeCell]
BaseChunk {.pure, inheritable.} = object
prevSize: int # size of previous chunk; for coalescing
# 0th bit == 1 if 'used
size: int # if < PageSize it is a small chunk
owner: ptr MemRegion
when usesRegionHandles:
owner: ptr RegionHandle
else:
owner: ptr MemRegion
SmallChunk = object of BaseChunk
next, prev: PSmallChunk # chunks of the same size
@@ -145,14 +154,16 @@ type
next: ptr HeapLinks
MemRegion = object
when usesRegionHandles:
regionHandle: ptr RegionHandle
when not defined(gcDestructors):
minLargeObj, maxLargeObj: int
freeSmallChunks: array[0..max(1, SmallChunkSize div MemAlign-1), PSmallChunk]
# List of available chunks per size class. Only one is expected to be active per class.
when defined(gcDestructors):
sharedFreeLists: array[0..max(1, SmallChunkSize div MemAlign-1), ptr FreeCell]
# When a thread frees a pointer it did not create, it must not adjust the counters.
# Instead, the cell is placed here and deferred until the next allocation.
sharedFreeLists: SharedFreeLists
# Used directly without threads. Threaded builds use RegionHandle but
# retain this 2 KiB spacer: removing it regresses 2-4 KiB allocations.
flBitmap: uint32
slBitmap: array[RealFli, uint32]
matrix: array[RealFli, array[MaxSli, PBigChunk]]
@@ -160,7 +171,7 @@ type
currMem, maxMem, freeMem, occ: int # memory sizes (allocated from OS)
lastSize: int # needed for the case that OS gives us pages linearly
when defined(gcDestructors):
sharedFreeListBigChunks: PBigChunk # make no attempt at avoiding false sharing for now for this object field
sharedFreeListBigChunks: PBigChunk # private pending list with threads; shared queue otherwise
chunkStarts: IntSet
when not defined(gcDestructors):
@@ -173,9 +184,24 @@ type
when defined(nimTypeNames):
allocCounter, deallocCounter: int
RegionHandle = object
# Permanent chunk-owner identity and home of the remote-free queues.
sharedFreeLists: SharedFreeLists
sharedFreeListBigChunks: PBigChunk
# Keep the movable allocator state with its permanent owner while the
# owning thread is retired.
region: MemRegion
next: ptr RegionHandle
template smallChunkOverhead(): untyped = sizeof(SmallChunk)
template bigChunkOverhead(): untyped = sizeof(BigChunk)
template regionOwner(a: var MemRegion): untyped =
when usesRegionHandles:
a.regionHandle
else:
addr a
when hasThreadSupport:
template loada(x: untyped): untyped = atomicLoadN(unsafeAddr x, ATOMIC_RELAXED)
template storea(x, y: untyped) = atomicStoreN(unsafeAddr x, y, ATOMIC_RELAXED)
@@ -502,6 +528,56 @@ proc pageAddr(p: pointer): PChunk {.inline.} =
result = cast[PChunk](cast[int](p) and not PageMask)
#sysAssert(Contains(allocator.chunkStarts, pageIndex(result)))
when hasThreadLocalAllocator:
var
regionPool: ptr RegionHandle
regionPoolLock: SysLock
initSysLock(regionPoolLock)
proc moveMemRegion(dest, source: ptr MemRegion) {.inline.} =
# MemRegion owns only raw allocator state, so transfer it bitwise and
# clear the source to leave exactly one owner.
copyMem(dest, source, sizeof(MemRegion))
zeroMem(source, sizeof(MemRegion))
proc acquireMemRegion(a: var MemRegion) {.raises: [], gcsafe.} =
if a.regionHandle != nil:
return
acquireSys(regionPoolLock)
let handle = regionPool
if handle != nil:
regionPool = handle.next
releaseSys(regionPoolLock)
if handle == nil:
# RegionHandle is larger than llAlloc's one-page metadata slabs and is
# retained independently of any checked-out MemRegion.
let handleSize = roundup(sizeof(RegionHandle), PageSize)
let newHandle = cast[ptr RegionHandle](osAllocPages(handleSize))
zeroMem(newHandle, sizeof(RegionHandle))
a.regionHandle = newHandle
else:
moveMemRegion(addr a, addr handle.region)
proc releaseMemRegion(a: var MemRegion) {.raises: [], gcsafe.} =
# Zeroing `a` also clears `a.regionHandle`, which is what keeps a late
# `dealloc` on this thread correct: the ownership test can no longer match,
# so the cell is routed to its real owner's handle instead of to a region
# that is about to be reused. A late *alloc* on the other hand would mint
# chunks with a nil owner, so nothing may allocate after this point --
# `afterThreadRuns` has already run by the time `threadProcWrapStackFrame`
# gets here.
if a.regionHandle == nil:
return
let handle = a.regionHandle
moveMemRegion(addr handle.region, addr a)
acquireSys(regionPoolLock)
handle.next = regionPool
regionPool = handle
releaseSys(regionPoolLock)
when false:
proc writeFreeList(a: MemRegion) =
var it = a.freeChunksList
@@ -618,7 +694,7 @@ proc splitChunk2(a: var MemRegion, c: PBigChunk, size: int): PBigChunk =
result.prev = nil
# size and not used:
result.prevSize = size
result.owner = addr a
result.owner = regionOwner(a)
sysAssert((size and 1) == 0, "splitChunk 2")
sysAssert((size and PageMask) == 0,
"splitChunk: size is not a multiple of the PageSize")
@@ -686,7 +762,7 @@ proc getBigChunk(a: var MemRegion, size: int): PBigChunk =
# if we over allocated split the chunk:
if result.size > size:
splitChunk(a, result, size)
result.owner = addr a
result.owner = regionOwner(a)
else:
removeChunkFromMatrix2(a, result, fl, sl)
if result.size >= size + PageSize:
@@ -694,7 +770,7 @@ proc getBigChunk(a: var MemRegion, size: int): PBigChunk =
# set 'used' to true:
result.prevSize = 1
track("setUsedToFalse", addr result.size, sizeof(int))
sysAssert result.owner == addr a, "getBigChunk: No owner set!"
sysAssert result.owner == regionOwner(a), "getBigChunk: No owner set!"
incl(a, a.chunkStarts, pageIndex(result))
dec(a.freeMem, size)
@@ -710,7 +786,7 @@ proc getHugeChunk(a: var MemRegion; size: int): PBigChunk =
result.size = size
# set 'used' to true:
result.prevSize = 1
result.owner = addr a
result.owner = regionOwner(a)
incl(a, a.chunkStarts, pageIndex(result))
proc freeHugeChunk(a: var MemRegion; c: PBigChunk) =
@@ -791,7 +867,7 @@ proc deallocBigChunk(a: var MemRegion, c: PBigChunk) =
when defined(gcDestructors):
template atomicPrepend(head, elem: untyped) =
# see also https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange
when hasThreadSupport:
when usesRegionHandles:
while true:
elem.next.storea head.loada
if atomicCompareExchangeN(addr head, addr elem.next, elem, weak = true, ATOMIC_RELEASE, ATOMIC_RELAXED):
@@ -800,30 +876,39 @@ when defined(gcDestructors):
elem.next.storea head.loada
head.storea elem
proc addToSharedFreeListBigChunks(a: var MemRegion; c: PBigChunk) {.inline.} =
sysAssert c.next == nil, "c.next pointer must be nil"
atomicPrepend a.sharedFreeListBigChunks, c
when usesRegionHandles:
proc addToSharedFreeListBigChunks(handle: ptr RegionHandle;
c: PBigChunk) {.inline.} =
sysAssert c.next == nil, "c.next pointer must be nil"
atomicPrepend handle.sharedFreeListBigChunks, c
else:
proc addToSharedFreeListBigChunks(a: var MemRegion;
c: PBigChunk) {.inline.} =
sysAssert c.next == nil, "c.next pointer must be nil"
atomicPrepend a.sharedFreeListBigChunks, c
proc takeFromSharedFreeListBigChunks(a: var MemRegion): PBigChunk {.inline.} =
when hasThreadSupport:
while true:
result = atomicLoadN(addr a.sharedFreeListBigChunks, ATOMIC_ACQUIRE)
if result == nil:
break
let next = result.next.loada
var expected = result
if atomicCompareExchangeN(addr a.sharedFreeListBigChunks, addr expected, next,
weak = true, ATOMIC_ACQUIRE, ATOMIC_RELAXED):
result.next.storea nil
break
else:
result = a.sharedFreeListBigChunks
if result != nil:
a.sharedFreeListBigChunks = result.next
result.next = nil
when usesRegionHandles:
if a.sharedFreeListBigChunks == nil:
let sharedHead = addr a.regionHandle.sharedFreeListBigChunks
# Detach a batch from the stable remote inbox. The embedded MemRegion
# field is now a private pending list and moves with the region.
if atomicLoadN(sharedHead, ATOMIC_RELAXED) != nil:
a.sharedFreeListBigChunks = atomicExchangeN(sharedHead, nil,
ATOMIC_ACQUIRE)
result = a.sharedFreeListBigChunks
if result != nil:
a.sharedFreeListBigChunks = result.next
result.next = nil
proc addToSharedFreeList(c: PSmallChunk; f: ptr FreeCell; size: int) {.inline.} =
atomicPrepend c.owner.sharedFreeLists[size], f
when usesRegionHandles:
proc addToSharedFreeList(handle: ptr RegionHandle; f: ptr FreeCell;
size: int) {.inline.} =
atomicPrepend handle.sharedFreeLists[size], f
else:
proc addToSharedFreeList(c: PSmallChunk; f: ptr FreeCell;
size: int) {.inline.} =
atomicPrepend c.owner.sharedFreeLists[size], f
const MaxSteps = 20
@@ -846,9 +931,8 @@ when defined(gcDestructors):
dec(a.occ, total)
proc freeDeferredObjects(a: var MemRegion) =
# Pop only as many nodes as we can process. Detaching the entire list and
# re-enqueuing its unprocessed tail through atomicPrepend would overwrite
# that tail's next pointer and lose the rest of the list.
# Bound the work per allocation. With threads, takeFromSharedFreeListBigChunks
# detaches the shared stack into the region's private pending list first.
for _ in 0..MaxSteps:
let it = takeFromSharedFreeListBigChunks(a)
if it == nil: break
@@ -892,17 +976,20 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer
if size + alignOff <= SmallChunkSize-smallChunkOverhead():
template fetchSharedCells(tc: PSmallChunk) =
# Consumes cells from (potentially) foreign threads from `a.sharedFreeLists[s]`
# Consume cells freed by potentially foreign threads.
when defined(gcDestructors):
if tc.freeList == nil:
when hasThreadSupport:
# Steal the entire list from `sharedFreeList`:
tc.freeList = atomicExchangeN(addr a.sharedFreeLists[s], nil, ATOMIC_RELAXED)
when usesRegionHandles:
let sharedHead = addr tc.owner.sharedFreeLists[s]
# The owner is the only consumer, so once it observes a non-empty
# stack no other thread can make it empty before the exchange.
if atomicLoadN(sharedHead, ATOMIC_RELAXED) != nil:
tc.freeList = atomicExchangeN(sharedHead, nil, ATOMIC_ACQUIRE)
else:
tc.freeList = a.sharedFreeLists[s]
a.sharedFreeLists[s] = nil
# if `tc.freeList` isn't nil, `tc` will gain capacity.
# We must calculate how much it gained and how many foreign cells are included.
# If `tc.freeList` isn't nil, `tc` gains capacity. Calculate how
# much it gained and how many foreign cells are included.
compensateCounters(a, tc, size)
# allocate a small block: for small chunks, we use only its next pointer
@@ -921,11 +1008,11 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer
c.size = size
c.acc = (alignOff + size).uint32
c.free = SmallChunkSize - smallChunkOverhead() - alignOff.int32 - size.int32
sysAssert c.owner == addr(a), "rawAlloc: No owner set!"
sysAssert c.owner == regionOwner(a), "rawAlloc: No owner set!"
c.next = nil
c.prev = nil
# Shared cells are fetched here in case `c.size * 2 >= SmallChunkSize - smallChunkOverhead()`.
# For those single cell chunks, we would otherwise have to allocate a new one almost every time.
# Fetch deferred cells here for single-cell chunks; otherwise every
# allocation of that size would tend to allocate a new chunk.
fetchSharedCells(c)
if c.free >= size:
# Because removals from `a.freeSmallChunks[s]` only happen in the other alloc branch and during dealloc,
@@ -963,9 +1050,8 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer
dec(c.free, size)
sysAssert((cast[int](result) and (MemAlign-1)) == 0, "rawAlloc 9")
sysAssert(allocInv(a), "rawAlloc: end c != nil")
# We fetch deferred cells *after* advancing `c.freeList`/`acc` to adjust `c.free`.
# If after the adjustment it turns out there's free cells available,
# the chunk stays in `a.freeSmallChunks[s]` and the need for a new chunk is delayed.
# Fetch after advancing `freeList`/`acc` so `c.free` can be adjusted. If
# cells arrived, keep this chunk active instead of allocating another.
fetchSharedCells(c)
sysAssert(allocInv(a), "rawAlloc: before c.free < size")
if c.free < size:
@@ -1030,7 +1116,8 @@ proc rawDealloc(a: var MemRegion, p: pointer) =
# ^ We might access thread foreign storage here.
# The other thread cannot possibly free this block as it's still alive.
var f = cast[ptr FreeCell](p)
if c.owner == addr(a):
let owner = c.owner
if owner == regionOwner(a):
# We own the block, there is no foreign thread involved.
dec a.occ, s
untrackSize(s)
@@ -1093,7 +1180,10 @@ proc rawDealloc(a: var MemRegion, p: pointer) =
when logAlloc: cprintf("dealloc(pointer_%p) # SMALL FROM %p CALLER %p\n", p, c.owner, addr(a))
when defined(gcDestructors):
addToSharedFreeList(c, f, s div MemAlign)
when usesRegionHandles:
addToSharedFreeList(owner, f, s div MemAlign)
else:
addToSharedFreeList(c, f, s div MemAlign)
sysAssert(((cast[int](p) and PageMask) - smallChunkOverhead() - c.chunkAlignOff) %%
s == 0, "rawDealloc 2")
else:
@@ -1101,10 +1191,14 @@ proc rawDealloc(a: var MemRegion, p: pointer) =
when overwriteFree: nimSetMem(p, -1'i32, c.size -% bigChunkOverhead())
when logAlloc: cprintf("dealloc(pointer_%p) # BIG %p\n", p, c.owner)
when defined(gcDestructors):
if c.owner == addr(a):
let owner = c.owner
if owner == regionOwner(a):
deallocBigChunk(a, cast[PBigChunk](c))
else:
addToSharedFreeListBigChunks(c.owner[], cast[PBigChunk](c))
when usesRegionHandles:
addToSharedFreeListBigChunks(owner, cast[PBigChunk](c))
else:
addToSharedFreeListBigChunks(owner[], cast[PBigChunk](c))
else:
deallocBigChunk(a, cast[PBigChunk](c))
@@ -1263,6 +1357,13 @@ when defined(nimTypeNames):
template instantiateForRegion(allocator: untyped) {.dirty.} =
{.push stackTrace: off.}
when hasThreadLocalAllocator:
proc initThreadAllocator() {.gcsafe, raises: [].} =
acquireMemRegion(allocator)
proc releaseThreadAllocator() {.gcsafe, raises: [].} =
releaseMemRegion(allocator)
when defined(nimFulldebug):
proc interiorAllocatedPtr*(p: pointer): pointer =
result = interiorAllocatedPtr(allocator, p)

View File

@@ -312,13 +312,17 @@ when not (defined(gcOrc) or defined(gcYrc)):
## Forces a full garbage collection pass. With `--mm:arc` a nop.
discard
template setupForeignThreadGc* =
## With `--mm:arc` a nop.
discard
template tearDownForeignThreadGc* =
## With `--mm:arc` a nop.
discard
when not hasThreadSupport:
template setupForeignThreadGc* = discard
template tearDownForeignThreadGc* = discard
elif emulatedThreadVars:
template setupForeignThreadGc* =
{.error: "setupForeignThreadGc is available only when ``--threads:on`` and ``--tlsEmulation:off`` are used".}
template tearDownForeignThreadGc* =
{.error: "tearDownForeignThreadGc is available only when ``--threads:on`` and ``--tlsEmulation:off`` are used".}
elif not hasThreadLocalAllocator:
template setupForeignThreadGc* = discard
template tearDownForeignThreadGc* = discard
proc isObjDisplayCheck(source: PNimTypeV2, targetDepth: int16, token: uint32): bool {.compilerRtl, inl.} =
result = targetDepth <= source.depth and source.display[targetDepth] == token

View File

@@ -19,6 +19,13 @@ when not defined(useNimRtl):
threadType = ThreadType.NimThread
when hasThreadLocalAllocator and not emulatedThreadVars:
proc setupForeignThreadGc*() {.gcsafe, raises: [].} =
initThreadAllocator()
proc tearDownForeignThreadGc*() {.gcsafe, raises: [].} =
releaseThreadAllocator()
when defined(gcDestructors):
proc deallocThreadStorage(p: pointer) = c_free(p)
else:
@@ -83,6 +90,8 @@ else:
deallocThreadStorage(thrd.rawStack)
proc threadProcWrapStackFrame[TArg](thrd: ptr Thread[TArg]) {.raises: [].} =
when hasThreadLocalAllocator:
initThreadAllocator()
when defined(boehmgc):
boehmGC_call_with_stack_base(threadProcWrapDispatch[TArg], thrd)
elif not defined(nogc) and not defined(gogc) and not defined(gcRegions) and not usesDestructors:
@@ -97,6 +106,8 @@ proc threadProcWrapStackFrame[TArg](thrd: ptr Thread[TArg]) {.raises: [].} =
when declared(deallocOsPages): deallocOsPages()
else:
threadProcWrapDispatch(thrd)
when hasThreadLocalAllocator:
releaseThreadAllocator()
template nimThreadProcWrapperBody*(closure: untyped): untyped =
var thrd = cast[ptr Thread[TArg]](closure)

View File

@@ -176,6 +176,7 @@ pkg "unittest2"
pkg "unpack"
when not defined(arm64):
pkg "weave", "nimble install -y cligen@#HEAD; nimble test_gc_arc", useHead = true
pkg "web3", "nimble test_slim", useHead = true
pkg "websock", "nim c -d:chronicles_log_level=INFO tests/all_tests.nim"
pkg "websocket", "nim c websocket.nim"
pkg "with"

View File

@@ -937,3 +937,47 @@ proc mainRegen() =
doAssert b.a.c == right
mainRegen()
from std/typetraits import distinctBase, supportsCopyMem
block: # bug #26025
type
M[B] = distinct seq[B]
W = object
g: U # `U` is only declared below, so it used to be a `tyForward`
# here and `W` ended up without `tfHasAsgn`
U = M[uint64]
doAssert not supportsCopyMem(W)
var h: M[W]
seq[W](h).add W(g: U(@[1'u64]))
var copied = h
for it in items(distinctBase(copied)):
doAssert seq[uint64](it.g) == @[1'u64]
doAssert seq[uint64](seq[W](h)[0].g) == @[1'u64]
block: # bug #26025, the propagation has to reach the indirect owners too
type
M2[B] = distinct seq[B]
ViaArray = object
g: array[2, Late] # the forward type sits inside the field's type
Outer = object # `Inner` is forward here...
a: Inner
Inner = object
b: Late
Late = M2[uint64]
Reader = object # ...whereas `Outer` is already reified but its
z: Outer # own flags were still provisional
AsTuple = tuple[a: Late]
doAssert not supportsCopyMem(ViaArray)
doAssert not supportsCopyMem(Inner)
doAssert not supportsCopyMem(Outer)
doAssert not supportsCopyMem(Reader)
doAssert not supportsCopyMem(AsTuple)

16
tests/ccgbugs/t26104.nim Normal file
View File

@@ -0,0 +1,16 @@
discard """
action: compile
ccodeCheck: "@'((*Result).f);' .*"
"""
# bug #26104: a compile-time-only `typeof` argument was treated as a runtime
# alias of the result field, forcing an unnecessarily large temporary and copy.
type
B = array[131072, byte]
Y = object
f: B
proc fill(_: type B): B = discard
proc make(): Y = result.f = fill(typeof(result.f))
discard make()

35
tests/ccgbugs/t26112.nim Normal file
View File

@@ -0,0 +1,35 @@
discard """
matrix: "--mm:refc; --mm:orc"
ccodeCheck: "'result.fromScalar = x_p0;'"
ccodeCheck: "'result.fromObject = x_p0.fromObject;'"
ccodeCheck: "'result.nested.fromNested = x_p0.fromNested;'"
"""
# bug #26112: unrelated parameters were considered potential aliases of the
# result location when their types could be contained in the returned object.
type
Inner = object
fromNested: int
P = object
fromScalar: int
fromObject: int
nested: Inner
func fromScalar(x: int): P =
P(fromScalar: x)
proc fromObject(x: P): P =
P(fromObject: x.fromObject)
func fromNested(x: Inner): P =
P(nested: Inner(fromNested: x.fromNested))
proc selfAlias(): P =
result.fromScalar = 42
result = P(fromScalar: result.fromScalar)
doAssert fromScalar(1).fromScalar == 1
doAssert fromObject(P(fromObject: 2)).fromObject == 2
doAssert fromNested(Inner(fromNested: 3)).nested.fromNested == 3
doAssert selfAlias().fromScalar == 42

View File

@@ -0,0 +1,56 @@
discard """
matrix: "--mm:refc; --mm:orc"
"""
type
A = object of RootObj
V = object
case g: bool
of true:
v: A
of false:
e: string
var r = V(g: true, v: A())
discard move r
GC_fullCollect()
type
Kind = enum nested, other
Nested = object
case kind: Kind
of nested:
case enabled: bool
of true: payload: A
of false: message: string
of other:
discard
var n = Nested(kind: nested, enabled: true, payload: A())
discard move n
GC_fullCollect()
# Moving from the other branch must keep its value alive and leave the source
# in the default state.
var s = V(g: false, e: "hello")
let moved = move s
doAssert moved.e == "hello"
doAssert not s.g
doAssert s.e.len == 0
# Reinitializing the zeroed value must also restore embedded object type
# headers.
type W = object
a: A
value: V
text: string
var w = W(a: A(), value: V(g: true, v: A()), text: "content")
let movedW = move w
doAssert movedW.text == "content"
doAssert cast[ptr pointer](addr w.a)[] != nil
doAssert not w.value.g
doAssert w.value.e.len == 0
doAssert w.text.len == 0
GC_fullCollect()

View File

@@ -0,0 +1,59 @@
discard """
matrix: "--mm:arc --threads:on --tlsEmulation:off; --mm:orc --threads:on --tlsEmulation:off"
disabled: "windows"
output: "ok"
timeout: "30"
"""
import std/posix
var
escaped: pointer
reused: pointer
proc allocateOnForeignThread(_: pointer): pointer {.noconv.} =
setupForeignThreadGc()
escaped = allocShared(96)
cast[ptr int](escaped)[] = 73
tearDownForeignThreadGc()
result = nil
proc reuseOnForeignThread(_: pointer): pointer {.noconv.} =
setupForeignThreadGc()
doAssert cast[ptr int](escaped)[] == 73
deallocShared(escaped)
reused = allocShared(96)
doAssert reused == escaped
deallocShared(reused)
tearDownForeignThreadGc()
result = nil
proc consumeDeferredFree(_: pointer): pointer {.noconv.} =
setupForeignThreadGc()
let first = allocShared(96)
let second = allocShared(96)
# The first allocation advances the active chunk and collects its deferred
# foreign frees. The next allocation reuses the remotely returned cell.
doAssert second == escaped
deallocShared(first)
deallocShared(second)
tearDownForeignThreadGc()
result = nil
proc run(worker: proc(_: pointer): pointer {.noconv.}) =
var thread: Pthread
doAssert pthread_create(addr thread, nil, worker, nil) == 0
doAssert pthread_join(thread, nil) == 0
# setup/teardown is the checkout/return boundary. A distinct native thread can
# safely inherit the allocator even while one of its allocations is still live.
run(allocateOnForeignThread)
run(reuseOnForeignThread)
# A free that arrives while the allocator is idle is queued on its handle and
# consumed after that allocator is handed to another foreign thread.
run(allocateOnForeignThread)
deallocShared(escaped)
run(consumeDeferredFree)
echo "ok"

View File

@@ -0,0 +1,64 @@
discard """
matrix: "--mm:arc --threads:on; --mm:orc --threads:on"
output: "ok"
timeout: "30"
"""
import std/[atomics, typedthreads]
const
pointerCount = 512
drainCount = 2048
iterations {.intdefine.} = 200
sizes = [16, 64, 4000, 4096, 8192]
var
pointers: array[pointerCount, pointer]
mayExit: Atomic[bool]
proc owner() {.thread.} =
for i in 0..<pointers.len:
let size = sizes[i mod sizes.len]
pointers[i] = allocShared(size)
cast[ptr byte](pointers[i])[] = byte(i)
proc borrower() {.thread.} =
while not mayExit.load(moAcquire):
discard
proc drain() {.thread.} =
var drained: array[drainCount, pointer]
for i in 0..<drained.len:
drained[i] = allocShared(sizes[i mod sizes.len])
for p in drained:
deallocShared(p)
let occupied = getOccupiedMem()
doAssert occupied == 0, "allocator retained " & $occupied & " bytes"
for _ in 0..<iterations:
# The owner retires with live small and big allocations. The borrower checks
# out that region while this already-running main thread returns the cells.
# This races foreign queue publication against both directions of the
# MemRegion handoff without creating an unbounded number of regions.
block:
var thread: Thread[void]
createThread(thread, owner)
joinThread(thread)
mayExit.store(false, moRelaxed)
var borrowerThread: Thread[void]
createThread(borrowerThread, borrower)
for i, p in pointers:
if i == pointers.len div 2:
# Let the borrower tear the allocator down while the second half of the
# foreign publications are still in flight.
mayExit.store(true, moRelease)
deallocShared(p)
joinThread(borrowerThread)
block:
var thread: Thread[void]
createThread(thread, drain)
joinThread(thread)
echo "ok"

View File

@@ -0,0 +1,92 @@
discard """
matrix: "--mm:arc --threads:on; --mm:orc --threads:on"
output: "ok"
timeout: "30"
"""
import std/[atomics, typedthreads]
const concurrentThreads = 4
var
escaped: pointer
reused: pointer
bigEscaped: pointer
roundAddresses: array[2, array[concurrentThreads, pointer]]
ready: Atomic[int]
mayExit: Atomic[bool]
proc allocateEscaped() {.thread.} =
escaped = allocShared(64)
cast[ptr int](escaped)[] = 42
proc consumeAfterHandoff() {.thread.} =
doAssert cast[ptr int](escaped)[] == 42
deallocShared(escaped)
reused = allocShared(64)
doAssert reused == escaped
deallocShared(reused)
# A live allocation can outlast its original thread. The next thread receives
# the same allocator and its stable handle makes the deallocation local again.
block:
var thread: Thread[void]
createThread(thread, allocateEscaped)
joinThread(thread)
createThread(thread, consumeAfterHandoff)
joinThread(thread)
proc allocateBigEscaped() {.thread.} =
bigEscaped = allocShared(8192)
cast[ptr int](bigEscaped)[] = 91
proc consumeBigAfterHandoff() {.thread.} =
doAssert cast[ptr int](bigEscaped)[] == 91
deallocShared(bigEscaped)
let p = allocShared(8192)
doAssert p == bigEscaped
deallocShared(p)
# Big chunks use a separate deferred-free queue on the stable handle.
block:
var thread: Thread[void]
createThread(thread, allocateBigEscaped)
joinThread(thread)
createThread(thread, consumeBigAfterHandoff)
joinThread(thread)
proc allocateConcurrently(arg: tuple[round, index: int]) {.thread.} =
let p = allocShared(80)
roundAddresses[arg.round][arg.index] = p
deallocShared(p)
discard ready.fetchAdd(1, moRelease)
while not mayExit.load(moAcquire):
discard
proc runConcurrentRound(round: int) =
var threads: array[concurrentThreads, Thread[tuple[round, index: int]]]
ready.store(0, moRelaxed)
mayExit.store(false, moRelaxed)
for i in 0..<threads.len:
createThread(threads[i], allocateConcurrently, (round, i))
while ready.load(moAcquire) != concurrentThreads:
discard
mayExit.store(true, moRelease)
for thread in threads.mitems:
joinThread(thread)
# The first round establishes the peak number of simultaneous allocators. The
# following rounds must reuse those regions instead of reserving one region per
# new thread.
runConcurrentRound(0)
for _ in 0..<32:
runConcurrentRound(1)
for p in roundAddresses[1]:
var found = false
for old in roundAddresses[0]:
if p == old:
found = true
break
doAssert found
echo "ok"

View File

@@ -0,0 +1,56 @@
discard """
matrix: "--mm:arc --threads:on; --mm:orc --threads:on"
output: "ok"
timeout: "30"
"""
import std/[atomics, typedthreads]
const
pointerCount = 1024
iterations = 100
var
pointers: array[pointerCount, pointer]
drainPointers: array[pointerCount, pointer]
ready: Atomic[bool]
start: Atomic[bool]
remoteDone: Atomic[bool]
proc owner() {.thread.} =
for i in 0..<pointers.len:
pointers[i] = allocShared(16 + (i mod 8) * 16)
ready.store(true, moRelease)
while not start.load(moAcquire):
discard
# Race allocator activity against remote frees. The owner can consume cells
# while the remote thread is still publishing entries to its handle.
while not remoteDone.load(moAcquire):
for i in 0..<8:
let p = allocShared(16 + i * 16)
deallocShared(p)
# Exhaust local free lists so all remaining remote lists are consumed before
# this allocator is returned to the pool.
for i in 0..<drainPointers.len:
drainPointers[i] = allocShared(16 + (i mod 8) * 16)
for p in drainPointers:
deallocShared(p)
doAssert getOccupiedMem() == 0
for _ in 0..<iterations:
ready.store(false, moRelaxed)
start.store(false, moRelaxed)
remoteDone.store(false, moRelaxed)
var thread: Thread[void]
createThread(thread, owner)
while not ready.load(moAcquire):
discard
start.store(true, moRelease)
for p in pointers:
deallocShared(p)
remoteDone.store(true, moRelease)
joinThread(thread)
echo "ok"