diff options
46 files changed, 1208 insertions, 1529 deletions
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index c9ff9d4f0..df3b655e3 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -959,6 +959,7 @@ proc genEcho(p: BProc, n: PNode) = addf(args, ", $1? ($1)->data:\"nil\"", [rdLoc(a)]) linefmt(p, cpsStmts, "printf($1$2);$n", makeCString(repeat("%s", n.len) & tnl), args) + linefmt(p, cpsStmts, "fflush(stdout);$n") proc gcUsage(n: PNode) = if gSelectedGC == gcNone: message(n.info, warnGcMem, n.renderTree) @@ -1415,11 +1416,11 @@ proc binaryExprIn(p: BProc, e: PNode, a, b, d: var TLoc, frmt: string) = proc genInExprAux(p: BProc, e: PNode, a, b, d: var TLoc) = case int(getSize(skipTypes(e.sons[1].typ, abstractVar))) - of 1: binaryExprIn(p, e, a, b, d, "(($1 &(1<<(($2)&7)))!=0)") - of 2: binaryExprIn(p, e, a, b, d, "(($1 &(1<<(($2)&15)))!=0)") - of 4: binaryExprIn(p, e, a, b, d, "(($1 &(1<<(($2)&31)))!=0)") - of 8: binaryExprIn(p, e, a, b, d, "(($1 &(IL64(1)<<(($2)&IL64(63))))!=0)") - else: binaryExprIn(p, e, a, b, d, "(($1[$2/8] &(1<<($2%8)))!=0)") + of 1: binaryExprIn(p, e, a, b, d, "(($1 &(1U<<((NU)($2)&7U)))!=0)") + of 2: binaryExprIn(p, e, a, b, d, "(($1 &(1U<<((NU)($2)&15U)))!=0)") + of 4: binaryExprIn(p, e, a, b, d, "(($1 &(1U<<((NU)($2)&31U)))!=0)") + of 8: binaryExprIn(p, e, a, b, d, "(($1 &((NU64)1<<((NU)($2)&63U)))!=0)") + else: binaryExprIn(p, e, a, b, d, "(($1[(NU)($2)>>3] &(1U<<((NU)($2)&7U)))!=0)") proc binaryStmtInExcl(p: BProc, e: PNode, d: var TLoc, frmt: string) = var a, b: TLoc @@ -1500,8 +1501,8 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) = else: internalError(e.info, "genSetOp()") else: case op - of mIncl: binaryStmtInExcl(p, e, d, "$1[$2/8] |=(1<<($2%8));$n") - of mExcl: binaryStmtInExcl(p, e, d, "$1[$2/8] &= ~(1<<($2%8));$n") + of mIncl: binaryStmtInExcl(p, e, d, "$1[(NU)($2)>>3] |=(1U<<($2&7U));$n") + of mExcl: binaryStmtInExcl(p, e, d, "$1[(NU)($2)>>3]] &= ~(1U<<($2&7U));$n") of mCard: unaryExprChar(p, e, d, "#cardSet($1, " & $size & ')') of mLtSet, mLeSet: getTemp(p, getSysType(tyInt), i) # our counter @@ -1733,8 +1734,6 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = of mEcho: genEcho(p, e[1].skipConv) of mArrToSeq: genArrToSeq(p, e, d) of mNLen..mNError, mSlurp..mQuoteAst: - echo "from here ", p.prc.name.s, " ", p.prc.info - writestacktrace() localError(e.info, errXMustBeCompileTime, e.sons[0].sym.name.s) of mSpawn: let n = lowerings.wrapProcForSpawn(p.module.module, e, e.typ, nil, nil) @@ -1788,11 +1787,11 @@ proc genSetConstr(p: BProc, e: PNode, d: var TLoc) = initLocExpr(p, e.sons[i].sons[0], a) initLocExpr(p, e.sons[i].sons[1], b) lineF(p, cpsStmts, "for ($1 = $3; $1 <= $4; $1++) $n" & - "$2[$1/8] |=(1<<($1%8));$n", [rdLoc(idx), rdLoc(d), + "$2[(NU)($1)>>3] |=(1U<<((NU)($1)&7U));$n", [rdLoc(idx), rdLoc(d), rdSetElemLoc(a, e.typ), rdSetElemLoc(b, e.typ)]) else: initLocExpr(p, e.sons[i], a) - lineF(p, cpsStmts, "$1[$2/8] |=(1<<($2%8));$n", + lineF(p, cpsStmts, "$1[(NU)($2)>>3] |=(1U<<((NU)($2)&7U));$n", [rdLoc(d), rdSetElemLoc(a, e.typ)]) else: # small set diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index f4a7c4400..73497bded 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -16,7 +16,7 @@ const # above X strings a hash-switch for strings is generated proc registerGcRoot(p: BProc, v: PSym) = - if gSelectedGC in {gcMarkAndSweep, gcGenerational} and + if gSelectedGC in {gcMarkAndSweep, gcGenerational, gcV2} and containsGarbageCollectedRef(v.loc.t): # we register a specialized marked proc here; this has the advantage # that it works out of the box for thread local storage then :-) diff --git a/compiler/jstypes.nim b/compiler/jstypes.nim index 367c173ea..611f50eaf 100644 --- a/compiler/jstypes.nim +++ b/compiler/jstypes.nim @@ -116,7 +116,7 @@ proc genEnumInfo(p: PProc, typ: PType, name: Rope) = [name, genTypeInfo(p, typ.sons[0])]) proc genTypeInfo(p: PProc, typ: PType): Rope = - let t = typ.skipTypes({tyGenericInst}) + let t = typ.skipTypes({tyGenericInst, tyDistinct}) result = "NTI$1" % [rope(t.id)] if containsOrIncl(p.g.typeInfoGenerated, t.id): return case t.kind diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index cccc94756..be1631af0 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -126,6 +126,7 @@ type fn, closureParam, state, resultSym: PSym # most are only valid if # fn.kind == skClosureIterator obj: PType + isIterator: bool PEnv = ref TEnv TEnv {.final.} = object of RootObj @@ -197,24 +198,29 @@ proc getHiddenParam(routine: PSym): PSym = result = hidden.sym assert sfFromGeneric in result.flags -proc getEnvParam(routine: PSym): PSym = +proc getEnvParam*(routine: PSym): PSym = let params = routine.ast.sons[paramsPos] let hidden = lastSon(params) if hidden.kind == nkSym and hidden.sym.name.s == paramName: result = hidden.sym assert sfFromGeneric in result.flags -proc initIter(iter: PSym): TIter = +proc initIter(iter: PSym; ptrType: PType = nil): TIter = result.fn = iter - if iter.kind == skClosureIterator: + result.isIterator = ptrType != nil or iter.kind == skClosureIterator + #echo "fuck you ", ptrType != nil + if result.isIterator: var cp = getEnvParam(iter) if cp == nil: - result.obj = createEnvObj(iter) + result.obj = if ptrType != nil: ptrType.lastSon else: createEnvObj(iter) cp = newSym(skParam, getIdent(paramName), iter, iter.info) incl(cp.flags, sfFromGeneric) - cp.typ = newType(tyRef, iter) - rawAddSon(cp.typ, result.obj) + if ptrType != nil: + cp.typ = ptrType + else: + cp.typ = newType(tyRef, iter) + rawAddSon(cp.typ, result.obj) addHiddenParam(iter, cp) else: result.obj = cp.typ.sons[0] @@ -534,6 +540,7 @@ proc searchForInnerProcs(o: POuterContext, n: PNode, env: PEnv) = let fn = n.sym if isInnerProc(fn, o.fn) and not containsOrIncl(o.processed, fn.id): let body = fn.getBody + if nfLL in body.flags: return # handle deeply nested captures: let ex = closureCreationPoint(body) @@ -794,7 +801,7 @@ proc finishEnvironments(o: POuterContext) = proc transformOuterProcBody(o: POuterContext, n: PNode; it: TIter): PNode = if nfLL in n.flags: result = nil - elif it.fn.kind == skClosureIterator: + elif it.isIterator: # unfortunately control flow is still convoluted and we can end up # multiple times here for the very same iterator. We shield against this # with some rather primitive check for now: @@ -843,7 +850,7 @@ proc transformOuterProc(o: POuterContext, n: PNode; it: TIter): PNode = if newBody != nil: local.ast.sons[bodyPos] = newBody - if it.fn.kind == skClosureIterator and interestingIterVar(local) and + if it.isIterator and interestingIterVar(local) and it.fn == local.owner: # every local goes through the closure: #if not containsOrIncl(o.capturedVars, local.id): @@ -931,7 +938,7 @@ proc transformOuterProc(o: POuterContext, n: PNode; it: TIter): PNode = when false: if n.sons[1].kind == nkSym: var local = n.sons[1].sym - if it.fn.kind == skClosureIterator and interestingIterVar(local) and + if it.isIterator and interestingIterVar(local) and it.fn == local.owner: # every local goes through the closure: addUniqueField(it.obj, local) @@ -941,14 +948,25 @@ proc transformOuterProc(o: POuterContext, n: PNode; it: TIter): PNode = if x != nil: n.sons[1] = x result = transformOuterConv(n) of nkYieldStmt: - if it.fn.kind == skClosureIterator: result = transformYield(o, n, it) + if it.isIterator: result = transformYield(o, n, it) else: outerProcSons(o, n, it) of nkReturnStmt: - if it.fn.kind == skClosureIterator: result = transformReturn(o, n, it) + if it.isIterator: result = transformReturn(o, n, it) else: outerProcSons(o, n, it) else: outerProcSons(o, n, it) +proc liftIterToProc*(fn: PSym; body: PNode; ptrType: PType): PNode = + var o = newOuterContext(fn) + let ex = closureCreationPoint(body) + let env = newEnv(o, nil, ex, fn) + addParamsToEnv(fn, env) + searchForInnerProcs(o, body, env) + createEnvironments(o) + let it = initIter(fn, ptrType) + result = transformOuterProcBody(o, body, it) + finishEnvironments(o) + proc liftLambdas*(fn: PSym, body: PNode): PNode = # XXX gCmd == cmdCompileToJS does not suffice! The compiletime stuff needs # the transformation even when compiling to JS ... diff --git a/compiler/parser.nim b/compiler/parser.nim index dbf9706ea..11dd6788a 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -991,7 +991,7 @@ proc isExprStart(p: TParser): bool = of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf, tkProc, tkIterator, tkBind, tkAddr, tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCharLit, tkVar, tkRef, tkPtr, - tkTuple, tkObject, tkType, tkWhen, tkCase: + tkTuple, tkObject, tkType, tkWhen, tkCase, tkOut: result = true else: result = false @@ -1038,7 +1038,7 @@ proc parseObject(p: var TParser): PNode proc parseTypeClass(p: var TParser): PNode proc primary(p: var TParser, mode: TPrimaryMode): PNode = - #| typeKeyw = 'var' | 'ref' | 'ptr' | 'shared' | 'tuple' + #| typeKeyw = 'var' | 'out' | 'ref' | 'ptr' | 'shared' | 'tuple' #| | 'proc' | 'iterator' | 'distinct' | 'object' | 'enum' #| primary = typeKeyw typeDescK #| / prefixOperator* identOrLiteral primarySuffix* @@ -1112,6 +1112,7 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = optInd(p, result) addSon(result, primary(p, pmNormal)) of tkVar: result = parseTypeDescKAux(p, nkVarTy, mode) + of tkOut: result = parseTypeDescKAux(p, nkVarTy, mode) of tkRef: result = parseTypeDescKAux(p, nkRefTy, mode) of tkPtr: result = parseTypeDescKAux(p, nkPtrTy, mode) of tkDistinct: result = parseTypeDescKAux(p, nkDistinctTy, mode) @@ -1763,7 +1764,7 @@ proc parseObject(p: var TParser): PNode = addSon(result, parseObjectPart(p)) proc parseTypeClassParam(p: var TParser): PNode = - if p.tok.tokType == tkVar: + if p.tok.tokType in {tkOut, tkVar}: result = newNodeP(nkVarTy, p) getTok(p) result.addSon(p.parseSymbol) @@ -1771,7 +1772,7 @@ proc parseTypeClassParam(p: var TParser): PNode = result = p.parseSymbol proc parseTypeClass(p: var TParser): PNode = - #| typeClassParam = ('var')? symbol + #| typeClassParam = ('var' | 'out')? symbol #| typeClass = typeClassParam ^* ',' (pragma)? ('of' typeDesc ^* ',')? #| &IND{>} stmt result = newNodeP(nkTypeClassTy, p) diff --git a/compiler/plugins.nim b/compiler/plugins.nim index 1c9b7b77b..19a0bc84d 100644 --- a/compiler/plugins.nim +++ b/compiler/plugins.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -## Plugin support for the Nim compiler. Right now there are no plugins and they +## Plugin support for the Nim compiler. Right now they ## need to be build with the compiler, no DLL support. import ast, semdata, idents @@ -20,13 +20,16 @@ type next: Plugin proc pluginMatches(p: Plugin; s: PSym): bool = - if s.name.id != p.fn.id: return false - let module = s.owner + if s.name.id != p.fn.id: + return false + let module = s.skipGenericOwner if module == nil or module.kind != skModule or - module.name.id != p.module.id: return false + module.name.id != p.module.id: + return false let package = module.owner if package == nil or package.kind != skPackage or - package.name.id != p.package.id: return false + package.name.id != p.package.id: + return false return true var head: Plugin diff --git a/compiler/plugins/active.nim b/compiler/plugins/active.nim index e9c11c2ea..7b6411178 100644 --- a/compiler/plugins/active.nim +++ b/compiler/plugins/active.nim @@ -10,4 +10,4 @@ ## Include file that imports all plugins that are active. import - locals.locals + locals.locals, itersgen diff --git a/compiler/plugins/itersgen.nim b/compiler/plugins/itersgen.nim new file mode 100644 index 000000000..91e1d2783 --- /dev/null +++ b/compiler/plugins/itersgen.nim @@ -0,0 +1,49 @@ +# +# +# The Nim Compiler +# (c) Copyright 2015 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Plugin to transform an inline iterator into a data structure. + +import plugins, ast, astalgo, magicsys, lookups, semdata, + lambdalifting, msgs, rodread + +proc iterToProcImpl(c: PContext, n: PNode): PNode = + result = newNodeI(nkStmtList, n.info) + let iter = n[1] + if iter.kind != nkSym or iter.sym.kind != skIterator: + localError(iter.info, "first argument needs to be an iterator") + return + if n[2].typ.isNil: + localError(n[2].info, "second argument needs to be a type") + return + if n[3].kind != nkIdent: + localError(n[3].info, "third argument needs to be an identifier") + return + + let t = n[2].typ.skipTypes({tyTypeDesc, tyGenericInst}) + if t.kind notin {tyRef, tyPtr} or t.lastSon.kind != tyObject: + localError(n[2].info, + "type must be a non-generic ref|ptr to object with state field") + return + let body = liftIterToProc(iter.sym, iter.sym.getBody, t) + + let prc = newSym(skProc, n[3].ident, iter.sym.owner, iter.sym.info) + prc.typ = copyType(iter.sym.typ, prc, false) + excl prc.typ.flags, tfCapturesEnv + prc.typ.n.add newSymNode(getEnvParam(iter.sym)) + prc.typ.rawAddSon t + let orig = iter.sym.ast + prc.ast = newProcNode(nkProcDef, n.info, + name = newSymNode(prc), + params = orig[paramsPos], + pragmas = orig[pragmasPos], + body = body) + prc.ast.add iter.sym.ast.sons[resultPos] + addInterfaceDecl(c, prc) + +registerPlugin("stdlib", "system", "iterToProc", iterToProcImpl) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 58c42d410..95a90463c 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -385,7 +385,8 @@ proc isOpImpl(c: PContext, n: PNode): PNode = result = newIntNode(nkIntLit, ord(t.kind == tyProc and t.callConv == ccClosure and tfIterator notin t.flags)) - else: discard + else: + result = newIntNode(nkIntLit, 0) else: var t2 = n[2].typ.skipTypes({tyTypeDesc}) maybeLiftType(t2, c, n.info) @@ -1434,20 +1435,15 @@ proc semYield(c: PContext, n: PNode): PNode = var iterType = c.p.owner.typ let restype = iterType.sons[0] if restype != nil: - let adjustedRes = if restype.kind == tyIter: restype.base - else: restype - if adjustedRes.kind != tyExpr: - n.sons[0] = fitNode(c, adjustedRes, n.sons[0]) + if restype.kind != tyExpr: + n.sons[0] = fitNode(c, restype, n.sons[0]) if n.sons[0].typ == nil: internalError(n.info, "semYield") - if resultTypeIsInferrable(adjustedRes): + if resultTypeIsInferrable(restype): let inferred = n.sons[0].typ - if restype.kind == tyIter: - restype.sons[0] = inferred - else: - iterType.sons[0] = inferred + iterType.sons[0] = inferred - semYieldVarResult(c, n, adjustedRes) + semYieldVarResult(c, n, restype) else: localError(n.info, errCannotReturnExpr) elif c.p.owner.typ.sons[0] != nil: @@ -1780,7 +1776,24 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = result = setMs(n, s) result.sons[1] = semExpr(c, n.sons[1]) result.typ = n[1].typ - else: result = semDirectOp(c, n, flags) + of mPlugin: + # semDirectOp with conditional 'afterCallActions': + let nOrig = n.copyTree + #semLazyOpAux(c, n) + result = semOverloadedCallAnalyseEffects(c, n, nOrig, flags) + if result == nil: + result = errorNode(c, n) + else: + let callee = result.sons[0].sym + if callee.magic == mNone: + semFinishOperands(c, result) + activate(c, result) + fixAbstractType(c, result) + analyseIfAddressTakenInCall(c, result) + if callee.magic != mNone: + result = magicsAfterOverloadResolution(c, result, flags) + else: + result = semDirectOp(c, n, flags) proc semWhen(c: PContext, n: PNode, semCheck = true): PNode = # If semCheck is set to false, ``when`` will return the verbatim AST of @@ -2167,7 +2180,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = message(n.info, warnDeprecated, "bind") result = semExpr(c, n.sons[0], flags) of nkTypeOfExpr, nkTupleTy, nkTupleClassTy, nkRefTy..nkEnumTy, nkStaticTy: - var typ = semTypeNode(c, n, nil).skipTypes({tyTypeDesc, tyIter}) + var typ = semTypeNode(c, n, nil).skipTypes({tyTypeDesc}) result.typ = makeTypeDesc(c, typ) #result = symNodeFromType(c, typ, n.info) of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: @@ -2240,7 +2253,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = var tupexp = semTuplePositionsConstr(c, n, flags) if isTupleType(tupexp): # reinterpret as type - var typ = semTypeNode(c, n, nil).skipTypes({tyTypeDesc, tyIter}) + var typ = semTypeNode(c, n, nil).skipTypes({tyTypeDesc}) result.typ = makeTypeDesc(c, typ) else: result = tupexp diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 29cce9247..c3a9e01a0 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -504,7 +504,8 @@ proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) = if n.kind == nkAddr: # addr(x[]) can't be proven, but addr(x) can: if not containsNode(n, {nkDerefExpr, nkHiddenDeref}): return - elif (n.kind == nkSym and n.sym.kind in routineKinds) or n.kind in procDefs: + elif (n.kind == nkSym and n.sym.kind in routineKinds) or + n.kind in procDefs+{nkObjConstr}: # 'p' is not nil obviously: return case impliesNotNil(tracked.guards, n) @@ -704,7 +705,15 @@ proc track(tracked: PEffects, n: PNode) = for i in 1 .. <len(n): trackOperand(tracked, n.sons[i], paramType(op, i)) if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}: # may not look like an assignment, but it is: - initVarViaNew(tracked, n.sons[1]) + let arg = n.sons[1] + initVarViaNew(tracked, arg) + if {tfNeedsInit} * arg.typ.lastSon.flags != {}: + if a.sym.magic == mNewSeq and n[2].kind in {nkCharLit..nkUInt64Lit} and + n[2].intVal == 0: + # var s: seq[notnil]; newSeq(s, 0) is a special case! + discard + else: + message(arg.info, warnProveInit, $arg) for i in 0 .. <safeLen(n): track(tracked, n.sons[i]) of nkDotExpr: @@ -875,7 +884,8 @@ proc trackProc*(s: PSym, body: PNode) = var t: TEffects initEffects(effects, s, t) track(t, body) - if not isEmptyType(s.typ.sons[0]) and tfNeedsInit in s.typ.sons[0].flags and + if not isEmptyType(s.typ.sons[0]) and + {tfNeedsInit, tfNotNil} * s.typ.sons[0].flags != {} and s.kind in {skProc, skConverter, skMethod}: var res = s.ast.sons[resultPos].sym # get result symbol if res.id notin t.init: diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index adb1c81c1..e80f1cfda 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -330,7 +330,8 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym = styleCheckDef(result) proc checkNilable(v: PSym) = - if sfGlobal in v.flags and {tfNotNil, tfNeedsInit} * v.typ.flags != {}: + if {sfGlobal, sfImportC} * v.flags == {sfGlobal} and + {tfNotNil, tfNeedsInit} * v.typ.flags != {}: if v.ast.isNil: message(v.info, warnProveInit, v.name.s) elif tfNotNil in v.typ.flags and tfNotNil notin v.ast.typ.flags: @@ -539,7 +540,7 @@ proc symForVar(c: PContext, n: PNode): PSym = proc semForVars(c: PContext, n: PNode): PNode = result = n var length = sonsLen(n) - let iterBase = n.sons[length-2].typ.skipTypes({tyIter}) + let iterBase = n.sons[length-2].typ var iter = skipTypes(iterBase, {tyGenericInst}) # length == 3 means that there is one for loop variable # and thus no tuple unpacking: @@ -593,8 +594,7 @@ proc semFor(c: PContext, n: PNode): PNode = result.kind = nkParForStmt else: result = semForFields(c, n, call.sons[0].sym.magic) - elif (isCallExpr and call.sons[0].typ.callConv == ccClosure) or - call.typ.kind == tyIter: + elif isCallExpr and call.sons[0].typ.callConv == ccClosure: # first class iterator: result = semForVars(c, n) elif not isCallExpr or call.sons[0].kind != nkSym or diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 65cb9421b..65edb756f 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -135,13 +135,19 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType = checkMinSonsLen(n, 1) var base = semTypeNode(c, n.lastSon, nil) result = newOrPrevType(kind, prev, c) + var isNilable = false # check every except the last is an object: for i in isCall .. n.len-2: - let region = semTypeNode(c, n[i], nil) - if region.skipTypes({tyGenericInst}).kind notin {tyError, tyObject}: - message n[i].info, errGenerated, "region needs to be an object type" - addSonSkipIntLit(result, region) + let ni = n[i] + if ni.kind == nkNilLit: + isNilable = true + else: + let region = semTypeNode(c, ni, nil) + if region.skipTypes({tyGenericInst}).kind notin {tyError, tyObject}: + message n[i].info, errGenerated, "region needs to be an object type" + addSonSkipIntLit(result, region) addSonSkipIntLit(result, base) + #if not isNilable: result.flags.incl tfNotNil proc semVarType(c: PContext, n: PNode, prev: PType): PType = if sonsLen(n) == 1: @@ -826,15 +832,6 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, result = newTypeWithSons(c, tyCompositeTypeClass, @[paramType, result]) result = addImplicitGeneric(result) - of tyIter: - if paramType.callConv == ccInline: - if procKind notin {skTemplate, skMacro, skIterator}: - localError(info, errInlineIteratorsAsProcParams) - if paramType.len == 1: - let lifted = liftingWalk(paramType.base) - if lifted != nil: paramType.sons[0] = lifted - result = addImplicitGeneric(paramType) - of tyGenericInst: if paramType.lastSon.kind == tyUserTypeClass: var cp = copyType(paramType, getCurrOwner(), false) @@ -865,11 +862,6 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, of tyUserTypeClass, tyBuiltInTypeClass, tyAnd, tyOr, tyNot: result = addImplicitGeneric(copyType(paramType, getCurrOwner(), true)) - of tyExpr: - if procKind notin {skMacro, skTemplate}: - result = addImplicitGeneric(newTypeS(tyAnything, c)) - #result = addImplicitGenericImpl(newTypeS(tyGenericParam, c), nil) - of tyGenericParam: markUsed(info, paramType.sym) styleCheckUse(info, paramType.sym) @@ -996,7 +988,8 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, # see tchainediterators # in cases like iterator foo(it: iterator): type(it) # we don't need to change the return type to iter[T] - if not r.isInlineIterator: r = newTypeWithSons(c, tyIter, @[r]) + result.flags.incl tfIterator + # XXX Would be nice if we could get rid of this result.sons[0] = r result.n.typ = r @@ -1151,7 +1144,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = # for ``type(countup(1,3))``, see ``tests/ttoseq``. checkSonsLen(n, 1) let typExpr = semExprWithType(c, n.sons[0], {efInTypeof}) - result = typExpr.typ.skipTypes({tyIter}) + result = typExpr.typ of nkPar: if sonsLen(n) == 1: result = semTypeNode(c, n.sons[0], prev) else: @@ -1169,6 +1162,14 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = semTypeNode(c, b, prev) elif ident != nil and ident.id == ord(wDotDot): result = semRangeAux(c, n, prev) + elif n[0].kind == nkNilLit and n.len == 2: + result = semTypeNode(c, n.sons[1], prev) + if result.skipTypes({tyGenericInst}).kind in NilableTypes+GenericTypes: + if tfNotNil in result.flags: + result = freshType(result, prev) + result.flags.excl(tfNotNil) + else: + localError(n.info, errGenerated, "invalid type") elif n[0].kind notin nkIdentKinds: result = semTypeExpr(c, n) else: @@ -1209,7 +1210,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = elif op.id == ord(wType): checkSonsLen(n, 2) let typExpr = semExprWithType(c, n.sons[1], {efInTypeof}) - result = typExpr.typ.skipTypes({tyIter}) + result = typExpr.typ else: result = semTypeExpr(c, n) of nkWhenStmt: @@ -1290,14 +1291,16 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result.flags.incl tfHasStatic of nkIteratorTy: if n.sonsLen == 0: - result = newConstraint(c, tyIter) + result = newTypeS(tyBuiltInTypeClass, c) + let child = newTypeS(tyProc, c) + child.flags.incl tfIterator + result.addSonSkipIntLit(child) else: result = semProcTypeWithScope(c, n, prev, skClosureIterator) + result.flags.incl(tfIterator) if n.lastSon.kind == nkPragma and hasPragma(n.lastSon, wInline): - result.kind = tyIter result.callConv = ccInline else: - result.flags.incl(tfIterator) result.callConv = ccClosure of nkProcTy: if n.sonsLen == 0: diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 7957ac50a..20b60a88d 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -77,7 +77,7 @@ proc cacheTypeInst*(inst: PType) = # update the refcount let gt = inst.sons[0] let t = if gt.kind == tyGenericBody: gt.lastSon else: gt - if t.kind in {tyStatic, tyGenericParam, tyIter} + tyTypeClasses: + if t.kind in {tyStatic, tyGenericParam} + tyTypeClasses: return gt.sym.typeInstCache.safeAdd(inst) @@ -390,7 +390,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = result = t if t == nil: return - if t.kind in {tyStatic, tyGenericParam, tyIter} + tyTypeClasses: + if t.kind in {tyStatic, tyGenericParam} + tyTypeClasses: let lookup = PType(idTableGet(cl.typeMap, t)) if lookup != nil: return lookup diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 9fda8c860..aee42e021 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1256,10 +1256,6 @@ proc localConvMatch(c: PContext, m: var TCandidate, f, a: PType, result.typ = getInstantiatedType(c, arg, m, base(f)) m.baseTypeMatch = true -proc isInlineIterator*(t: PType): bool = - result = t.kind == tyIter or - (t.kind == tyBuiltInTypeClass and t.base.kind == tyIter) - proc incMatches(m: var TCandidate; r: TTypeRelation; convMatch = 1) = case r of isConvertible, isIntConv: inc(m.convMatches, convMatch) @@ -1323,13 +1319,6 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, else: return argSemantized # argOrig - if r != isNone and f.isInlineIterator: - var inlined = newTypeS(tyStatic, c) - inlined.sons = @[argType] - inlined.n = argSemantized - put(m.bindings, f, inlined) - return argSemantized - # If r == isBothMetaConvertible then we rerun typeRel. # bothMetaCounter is for safety to avoid any infinite loop, # I don't have any example when it is needed. diff --git a/compiler/transf.nim b/compiler/transf.nim index 3d78a6b92..3a5ff982e 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -478,9 +478,8 @@ proc transformFor(c: PTransf, n: PNode): PTransNode = result[1] = newNode(nkEmpty).PTransNode return result c.breakSyms.add(labl) - if call.typ.kind != tyIter and - (call.kind notin nkCallKinds or call.sons[0].kind != nkSym or - call.sons[0].sym.kind != skIterator): + if call.kind notin nkCallKinds or call.sons[0].kind != nkSym or + call.sons[0].sym.kind != skIterator: n.sons[length-1] = transformLoopBody(c, n.sons[length-1]).PNode result[1] = lambdalifting.liftForLoop(n).PTransNode discard c.breakSyms.pop @@ -512,7 +511,6 @@ proc transformFor(c: PTransf, n: PNode): PTransNode = for i in countup(1, sonsLen(call) - 1): var arg = transform(c, call.sons[i]).PNode var formal = skipTypes(iter.typ, abstractInst).n.sons[i].sym - if arg.typ.kind == tyIter: continue case putArgInto(arg, formal.typ) of paDirectMapping: idNodeTablePut(newC.mapping, formal, arg) diff --git a/doc/astspec.txt b/doc/astspec.txt index c84fad8e8..f235e2984 100644 --- a/doc/astspec.txt +++ b/doc/astspec.txt @@ -924,9 +924,11 @@ AST: .. code-block:: nim nnkLetSection( - nnkIdentDefs(!"v"), - nnkEmpty(), # for the type - nnkIntLit(3) + nnkIdentDefs( + nnkIdent(!"a"), + nnkEmpty(), # or nnkIdent(...) for the type + nnkIntLit(3), + ) ) Const section diff --git a/doc/grammar.txt b/doc/grammar.txt index 72dc6c974..d967bf938 100644 --- a/doc/grammar.txt +++ b/doc/grammar.txt @@ -35,10 +35,13 @@ castExpr = 'cast' '[' optInd typeDesc optPar ']' '(' optInd expr optPar ')' parKeyw = 'discard' | 'include' | 'if' | 'while' | 'case' | 'try' | 'finally' | 'except' | 'for' | 'block' | 'const' | 'let' | 'when' | 'var' | 'mixin' -par = '(' optInd (&parKeyw complexOrSimpleStmt ^+ ';' - | simpleExpr ('=' expr (';' complexOrSimpleStmt ^+ ';' )? )? - | (':' expr)? (',' (exprColonEqExpr comma?)*)? )? - optPar ')' +par = '(' optInd + ( &parKeyw complexOrSimpleStmt ^+ ';' + | ';' complexOrSimpleStmt ^+ ';' + | pragmaStmt + | simpleExpr ( ('=' expr (';' complexOrSimpleStmt ^+ ';' )? ) + | (':' expr (',' exprColonEqExpr ^+ ',' )? ) ) ) + optPar ')' literal = | INT_LIT | INT8_LIT | INT16_LIT | INT32_LIT | INT64_LIT | UINT_LIT | UINT8_LIT | UINT16_LIT | UINT32_LIT | UINT64_LIT | FLOAT_LIT | FLOAT32_LIT | FLOAT64_LIT @@ -86,7 +89,7 @@ expr = (ifExpr | caseExpr | tryExpr) / simpleExpr -typeKeyw = 'var' | 'ref' | 'ptr' | 'shared' | 'tuple' +typeKeyw = 'var' | 'out' | 'ref' | 'ptr' | 'shared' | 'tuple' | 'proc' | 'iterator' | 'distinct' | 'object' | 'enum' primary = typeKeyw typeDescK / prefixOperator* identOrLiteral primarySuffix* @@ -165,7 +168,7 @@ objectCase = 'case' identWithPragma ':' typeDesc ':'? COMMENT? objectPart = IND{>} objectPart^+IND{=} DED / objectWhen / objectCase / 'nil' / 'discard' / declColonEquals object = 'object' pragma? ('of' typeDesc)? COMMENT? objectPart -typeClassParam = ('var')? symbol +typeClassParam = ('var' | 'out')? symbol typeClass = typeClassParam ^* ',' (pragma)? ('of' typeDesc ^* ',')? &IND{>} stmt typeDef = identWithPragma genericParamList? '=' optInd typeDefAux diff --git a/doc/lib.txt b/doc/lib.txt index 3dc58eebf..4fa49095c 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -374,6 +374,9 @@ Miscellaneous * `logging <logging.html>`_ This module implements a simple logger. +* `options <options.html>`_ + Types which encapsulate an optional value. + * `future <future.html>`_ This module implements new experimental features. Currently the syntax sugar for anonymous procedures. diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt index a73e22988..07d98b289 100644 --- a/doc/manual/generics.txt +++ b/doc/manual/generics.txt @@ -213,7 +213,7 @@ Concepts are written in the following form: Container[T] = concept c c.len is Ordinal - items(c) is iterator + items(c) is T for value in c: type(value) is T diff --git a/doc/tut1.txt b/doc/tut1.txt index 7dce8a218..747c1a3ff 100644 --- a/doc/tut1.txt +++ b/doc/tut1.txt @@ -758,19 +758,18 @@ However, this cannot be done for mutually recursive procedures: # forward declaration: proc even(n: int): bool -proc even(n: int): bool - -proc odd(n: int): bool = - assert(n >= 0) # makes sure we don't run into negative recursion - if n == 0: false - else: - n == 1 or even(n-1) +.. code-block:: nim + proc odd(n: int): bool = + assert(n >= 0) # makes sure we don't run into negative recursion + if n == 0: false + else: + n == 1 or even(n-1) -proc even(n: int): bool = - assert(n >= 0) # makes sure we don't run into negative recursion - if n == 1: false - else: - n == 0 or odd(n-1) + proc even(n: int): bool = + assert(n >= 0) # makes sure we don't run into negative recursion + if n == 1: false + else: + n == 0 or odd(n-1) Here ``odd`` depends on ``even`` and vice versa. Thus ``even`` needs to be introduced to the compiler before it is completely defined. The syntax for diff --git a/lib/js/dom.nim b/lib/js/dom.nim index b063fa838..11df959d7 100644 --- a/lib/js/dom.nim +++ b/lib/js/dom.nim @@ -14,36 +14,35 @@ when not defined(js) and not defined(Nimdoc): {.error: "This module only works on the JavaScript platform".} type - TEventHandlers* {.importc.} = object of RootObj - onabort*: proc (event: ref TEvent) {.nimcall.} - onblur*: proc (event: ref TEvent) {.nimcall.} - onchange*: proc (event: ref TEvent) {.nimcall.} - onclick*: proc (event: ref TEvent) {.nimcall.} - ondblclick*: proc (event: ref TEvent) {.nimcall.} - onerror*: proc (event: ref TEvent) {.nimcall.} - onfocus*: proc (event: ref TEvent) {.nimcall.} - onkeydown*: proc (event: ref TEvent) {.nimcall.} - onkeypress*: proc (event: ref TEvent) {.nimcall.} - onkeyup*: proc (event: ref TEvent) {.nimcall.} - onload*: proc (event: ref TEvent) {.nimcall.} - onmousedown*: proc (event: ref TEvent) {.nimcall.} - onmousemove*: proc (event: ref TEvent) {.nimcall.} - onmouseout*: proc (event: ref TEvent) {.nimcall.} - onmouseover*: proc (event: ref TEvent) {.nimcall.} - onmouseup*: proc (event: ref TEvent) {.nimcall.} - onreset*: proc (event: ref TEvent) {.nimcall.} - onselect*: proc (event: ref TEvent) {.nimcall.} - onsubmit*: proc (event: ref TEvent) {.nimcall.} - onunload*: proc (event: ref TEvent) {.nimcall.} - - addEventListener*: proc(ev: cstring, cb: proc(ev: ref TEvent), useCapture: bool = false) {.nimcall.} + EventTarget* = ref EventTargetObj + EventTargetObj {.importc.} = object of RootObj + onabort*: proc (event: Event) {.nimcall.} + onblur*: proc (event: Event) {.nimcall.} + onchange*: proc (event: Event) {.nimcall.} + onclick*: proc (event: Event) {.nimcall.} + ondblclick*: proc (event: Event) {.nimcall.} + onerror*: proc (event: Event) {.nimcall.} + onfocus*: proc (event: Event) {.nimcall.} + onkeydown*: proc (event: Event) {.nimcall.} + onkeypress*: proc (event: Event) {.nimcall.} + onkeyup*: proc (event: Event) {.nimcall.} + onload*: proc (event: Event) {.nimcall.} + onmousedown*: proc (event: Event) {.nimcall.} + onmousemove*: proc (event: Event) {.nimcall.} + onmouseout*: proc (event: Event) {.nimcall.} + onmouseover*: proc (event: Event) {.nimcall.} + onmouseup*: proc (event: Event) {.nimcall.} + onreset*: proc (event: Event) {.nimcall.} + onselect*: proc (event: Event) {.nimcall.} + onsubmit*: proc (event: Event) {.nimcall.} + onunload*: proc (event: Event) {.nimcall.} Window* = ref WindowObj - WindowObj {.importc.} = object of TEventHandlers + WindowObj {.importc.} = object of EventTargetObj document*: Document - event*: ref TEvent - history*: ref THistory - location*: ref TLocation + event*: Event + history*: History + location*: Location closed*: bool defaultStatus*: cstring innerHeight*, innerWidth*: int @@ -57,50 +56,15 @@ type statusbar*: ref TStatusBar status*: cstring toolbar*: ref TToolBar - - alert*: proc (msg: cstring) {.nimcall.} - back*: proc () {.nimcall.} - blur*: proc () {.nimcall.} - captureEvents*: proc (eventMask: int) {.nimcall.} - clearInterval*: proc (interval: ref TInterval) {.nimcall.} - clearTimeout*: proc (timeout: ref TTimeOut) {.nimcall.} - close*: proc () {.nimcall.} - confirm*: proc (msg: cstring): bool {.nimcall.} - disableExternalCapture*: proc () {.nimcall.} - enableExternalCapture*: proc () {.nimcall.} - find*: proc (text: cstring, caseSensitive = false, - backwards = false) {.nimcall.} - focus*: proc () {.nimcall.} - forward*: proc () {.nimcall.} - handleEvent*: proc (e: ref TEvent) {.nimcall.} - home*: proc () {.nimcall.} - moveBy*: proc (x, y: int) {.nimcall.} - moveTo*: proc (x, y: int) {.nimcall.} - open*: proc (uri, windowname: cstring, - properties: cstring = nil): Window {.nimcall.} - print*: proc () {.nimcall.} - prompt*: proc (text, default: cstring): cstring {.nimcall.} - releaseEvents*: proc (eventMask: int) {.nimcall.} - resizeBy*: proc (x, y: int) {.nimcall.} - resizeTo*: proc (x, y: int) {.nimcall.} - routeEvent*: proc (event: ref TEvent) {.nimcall.} - scrollBy*: proc (x, y: int) {.nimcall.} - scrollTo*: proc (x, y: int) {.nimcall.} - setInterval*: proc (code: cstring, pause: int): ref TInterval {.nimcall.} - setTimeout*: proc (code: cstring, pause: int): ref TTimeOut {.nimcall.} - stop*: proc () {.nimcall.} frames*: seq[TFrame] Frame* = ref FrameObj FrameObj {.importc.} = object of WindowObj - ClassList* {.importc.} = object of RootObj - add*: proc (class: cstring) {.nimcall.} - remove*: proc (class: cstring) {.nimcall.} - contains*: proc (class: cstring):bool {.nimcall.} - toggle*: proc (class: cstring) {.nimcall.} + ClassList* = ref ClassListObj + ClassListObj {.importc.} = object of RootObj - TNodeType* = enum + NodeType* = enum ElementNode = 1, AttributeNode, TextNode, @@ -115,7 +79,7 @@ type NotationNode Node* = ref NodeObj - NodeObj {.importc.} = object of TEventHandlers + NodeObj {.importc.} = object of EventTargetObj attributes*: seq[Node] childNodes*: seq[Node] children*: seq[Node] @@ -124,29 +88,12 @@ type lastChild*: Node nextSibling*: Node nodeName*: cstring - nodeType*: TNodeType + nodeType*: NodeType nodeValue*: cstring parentNode*: Node previousSibling*: Node - appendChild*: proc (child: Node) {.nimcall.} - appendData*: proc (data: cstring) {.nimcall.} - cloneNode*: proc (copyContent: bool): Node {.nimcall.} - deleteData*: proc (start, len: int) {.nimcall.} - getAttribute*: proc (attr: cstring): cstring {.nimcall.} - getAttributeNode*: proc (attr: cstring): Node {.nimcall.} - hasChildNodes*: proc (): bool {.nimcall.} innerHTML*: cstring - insertBefore*: proc (newNode, before: Node) {.nimcall.} - insertData*: proc (position: int, data: cstring) {.nimcall.} - removeAttribute*: proc (attr: cstring) {.nimcall.} - removeAttributeNode*: proc (attr: Node) {.nimcall.} - removeChild*: proc (child: Node) {.nimcall.} - replaceChild*: proc (newNode, oldNode: Node) {.nimcall.} - replaceData*: proc (start, len: int, text: cstring) {.nimcall.} - scrollIntoView*: proc () {.nimcall.} - setAttribute*: proc (name, value: cstring) {.nimcall.} - setAttributeNode*: proc (attr: Node) {.nimcall.} - style*: ref TStyle + style*: Style Document* = ref DocumentObj DocumentObj {.importc.} = object of NodeObj @@ -164,31 +111,16 @@ type title*: cstring URL*: cstring vlinkColor*: cstring - captureEvents*: proc (eventMask: int) {.nimcall.} - createAttribute*: proc (identifier: cstring): Node {.nimcall.} - createElement*: proc (identifier: cstring): Element {.nimcall.} - createTextNode*: proc (identifier: cstring): Node {.nimcall.} - getElementById*: proc (id: cstring): Element {.nimcall.} - getElementsByName*: proc (name: cstring): seq[Element] {.nimcall.} - getElementsByTagName*: proc (name: cstring): seq[Element] {.nimcall.} - getElementsByClassName*: proc (name: cstring): seq[Element] {.nimcall.} - getSelection*: proc (): cstring {.nimcall.} - handleEvent*: proc (event: ref TEvent) {.nimcall.} - open*: proc () {.nimcall.} - releaseEvents*: proc (eventMask: int) {.nimcall.} - routeEvent*: proc (event: ref TEvent) {.nimcall.} - write*: proc (text: cstring) {.nimcall.} - writeln*: proc (text: cstring) {.nimcall.} anchors*: seq[AnchorElement] forms*: seq[FormElement] images*: seq[ImageElement] - applets*: seq[ref TApplet] + applets*: seq[Element] embeds*: seq[EmbedElement] links*: seq[LinkElement] Element* = ref ElementObj ElementObj {.importc.} = object of NodeObj - classList*: ref Classlist + classList*: Classlist checked*: bool defaultChecked*: bool defaultValue*: cstring @@ -196,14 +128,7 @@ type form*: FormElement name*: cstring readOnly*: bool - blur*: proc () {.nimcall.} - click*: proc () {.nimcall.} - focus*: proc () {.nimcall.} - handleEvent*: proc (event: ref TEvent) {.nimcall.} - select*: proc () {.nimcall.} options*: seq[OptionElement] - getElementsByTagName*: proc (name: cstring): seq[Element] {.nimcall.} - getElementsByClassName*: proc (name: cstring): seq[Element] {.nimcall.} LinkElement* = ref LinkObj LinkObj {.importc.} = object of ElementObj @@ -220,16 +145,12 @@ type width*: int `type`*: cstring vspace*: int - play*: proc () {.nimcall.} - stop*: proc () {.nimcall.} AnchorElement* = ref AnchorObj AnchorObj {.importc.} = object of ElementObj text*: cstring x*, y*: int - TApplet* {.importc.} = object of RootObj - OptionElement* = ref OptionObj OptionObj {.importc.} = object of ElementObj defaultSelected*: bool @@ -244,8 +165,6 @@ type encoding*: cstring `method`*: cstring target*: cstring - reset*: proc () {.nimcall.} - submit*: proc () {.nimcall.} elements*: seq[Element] ImageElement* = ref ImageObj @@ -259,8 +178,8 @@ type vspace*: int width*: int - - TStyle* {.importc.} = object of RootObj + Style = ref StyleObj + StyleObj {.importc.} = object of RootObj background*: cstring backgroundAttachment*: cstring backgroundColor*: cstring @@ -350,11 +269,9 @@ type width*: cstring wordSpacing*: cstring zIndex*: int - getAttribute*: proc (attr: cstring, caseSensitive=false): cstring {.nimcall.} - removeAttribute*: proc (attr: cstring, caseSensitive=false) {.nimcall.} - setAttribute*: proc (attr, value: cstring, caseSensitive=false) {.nimcall.} - TEvent* {.importc.} = object of RootObj + Event* = ref EventObj + EventObj {.importc.} = object of RootObj target*: Node altKey*, ctrlKey*, shiftKey*: bool button*: int @@ -393,7 +310,8 @@ type SUBMIT*: int UNLOAD*: int - TLocation* {.importc.} = object of RootObj + Location* = ref LocationObj + LocationObj {.importc.} = object of RootObj hash*: cstring host*: cstring hostname*: cstring @@ -402,16 +320,13 @@ type port*: cstring protocol*: cstring search*: cstring - reload*: proc () {.nimcall.} - replace*: proc (s: cstring) {.nimcall.} - THistory* {.importc.} = object of RootObj + History* = ref HistoryObj + HistoryObj {.importc.} = object of RootObj length*: int - back*: proc () {.nimcall.} - forward*: proc () {.nimcall.} - go*: proc (pagesToJump: int) {.nimcall.} - TNavigator* {.importc.} = object of RootObj + Navigator* = ref NavigatorObj + NavigatorObj {.importc.} = object of RootObj appCodeName*: cstring appName*: cstring appVersion*: cstring @@ -419,7 +334,6 @@ type language*: cstring platform*: cstring userAgent*: cstring - javaEnabled*: proc (): bool {.nimcall.} mimeTypes*: seq[ref TMimeType] TPlugin* {.importc.} = object of RootObj @@ -441,7 +355,8 @@ type TToolBar* = TLocationBar TStatusBar* = TLocationBar - TScreen* {.importc.} = object of RootObj + Screen = ref ScreenObj + ScreenObj {.importc.} = object of RootObj availHeight*: int availWidth*: int colorDepth*: int @@ -452,11 +367,127 @@ type TTimeOut* {.importc.} = object of RootObj TInterval* {.importc.} = object of RootObj +{.push importcpp.} + +# EventTarget "methods" +proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), useCapture: bool = false) + +# Window "methods" +proc alert*(w: Window, msg: cstring) +proc back*(w: Window) +proc blur*(w: Window) +proc captureEvents*(w: Window, eventMask: int) {.deprecated.} +proc clearInterval*(w: Window, interval: ref TInterval) +proc clearTimeout*(w: Window, timeout: ref TTimeOut) +proc close*(w: Window) +proc confirm*(w: Window, msg: cstring): bool +proc disableExternalCapture*(w: Window) +proc enableExternalCapture*(w: Window) +proc find*(w: Window, text: cstring, caseSensitive = false, + backwards = false) +proc focus*(w: Window) +proc forward*(w: Window) +proc handleEvent*(w: Window, e: Event) +proc home*(w: Window) +proc moveBy*(w: Window, x, y: int) +proc moveTo*(w: Window, x, y: int) +proc open*(w: Window, uri, windowname: cstring, + properties: cstring = nil): Window +proc print*(w: Window) +proc prompt*(w: Window, text, default: cstring): cstring +proc releaseEvents*(w: Window, eventMask: int) {.deprecated.} +proc resizeBy*(w: Window, x, y: int) +proc resizeTo*(w: Window, x, y: int) +proc routeEvent*(w: Window, event: Event) +proc scrollBy*(w: Window, x, y: int) +proc scrollTo*(w: Window, x, y: int) +proc setInterval*(w: Window, code: cstring, pause: int): ref TInterval +proc setTimeout*(w: Window, code: cstring, pause: int): ref TTimeOut +proc stop*(w: Window) + +# Node "methods" +proc appendChild*(n, child: Node) +proc appendData*(n: Node, data: cstring) +proc cloneNode*(n: Node, copyContent: bool): Node +proc deleteData*(n: Node, start, len: int) +proc getAttribute*(n: Node, attr: cstring): cstring +proc getAttributeNode*(n: Node, attr: cstring): Node +proc hasChildNodes*(n: Node): bool +proc insertBefore*(n, newNode, before: Node) +proc insertData*(n: Node, position: int, data: cstring) +proc removeAttribute*(n: Node, attr: cstring) +proc removeAttributeNode*(n, attr: Node) +proc removeChild*(n, child: Node) +proc replaceChild*(n, newNode, oldNode: Node) +proc replaceData*(n: Node, start, len: int, text: cstring) +proc scrollIntoView*(n: Node) +proc setAttribute*(n: Node, name, value: cstring) +proc setAttributeNode*(n: Node, attr: Node) + +# Document "methods" +proc captureEvents*(d: Document, eventMask: int) {.deprecated.} +proc createAttribute*(d: Document, identifier: cstring): Node +proc createElement*(d: Document, identifier: cstring): Element +proc createTextNode*(d: Document, identifier: cstring): Node +proc getElementById*(d: Document, id: cstring): Element +proc getElementsByName*(d: Document, name: cstring): seq[Element] +proc getElementsByTagName*(d: Document, name: cstring): seq[Element] +proc getElementsByClassName*(d: Document, name: cstring): seq[Element] +proc getSelection*(d: Document): cstring +proc handleEvent*(d: Document, event: Event) +proc open*(d: Document) +proc releaseEvents*(d: Document, eventMask: int) {.deprecated.} +proc routeEvent*(d: Document, event: Event) +proc write*(d: Document, text: cstring) +proc writeln*(d: Document, text: cstring) + +# Element "methods" +proc blur*(e: Element) +proc click*(e: Element) +proc focus*(e: Element) +proc handleEvent*(e: Element, event: Event) +proc select*(e: Element) +proc getElementsByTagName*(e: Element, name: cstring): seq[Element] +proc getElementsByClassName*(e: Element, name: cstring): seq[Element] + +# FormElement "methods" +proc reset*(f: FormElement) +proc submit*(f: FormElement) + +# EmbedElement "methods" +proc play*(e: EmbedElement) +proc stop*(e: EmbedElement) + +# Location "methods" +proc reload*(loc: Location) +proc replace*(loc: Location, s: cstring) + +# History "methods" +proc back*(h: History) +proc forward*(h: History) +proc go*(h: History, pagesToJump: int) + +# Navigator "methods" +proc javaEnabled*(h: Navigator): bool + +# ClassList "methods" +proc add*(c: ClassList, class: cstring) +proc remove*(c: ClassList, class: cstring) +proc contains*(c: ClassList, class: cstring):bool +proc toggle*(c: ClassList, class: cstring) + +# Style "methods" +proc getAttribute*(s: Style, attr: cstring, caseSensitive=false): cstring +proc removeAttribute*(s: Style, attr: cstring, caseSensitive=false) +proc setAttribute*(s: Style, attr, value: cstring, caseSensitive=false) + +{.pop.} + var window* {.importc, nodecl.}: Window document* {.importc, nodecl.}: Document - navigator* {.importc, nodecl.}: ref TNavigator - screen* {.importc, nodecl.}: ref TScreen + navigator* {.importc, nodecl.}: Navigator + screen* {.importc, nodecl.}: Screen proc decodeURI*(uri: cstring): cstring {.importc, nodecl.} proc encodeURI*(uri: cstring): cstring {.importc, nodecl.} @@ -474,6 +505,7 @@ proc parseInt*(s: cstring, radix: int):int {.importc, nodecl.} type + TEventHandlers* {.deprecated.} = EventTargetObj TWindow* {.deprecated.} = WindowObj TFrame* {.deprecated.} = FrameObj TNode* {.deprecated.} = NodeObj @@ -485,3 +517,11 @@ type TOption* {.deprecated.} = OptionObj TForm* {.deprecated.} = FormObj TImage* {.deprecated.} = ImageObj + TNodeType* {.deprecated.} = NodeType + TEvent* {.deprecated.} = EventObj + TLocation* {.deprecated.} = LocationObj + THistory* {.deprecated.} = HistoryObj + TNavigator* {.deprecated.} = NavigatorObj + TStyle* {.deprecated.} = StyleObj + TScreen* {.deprecated.} = ScreenObj + TApplet* {.importc, deprecated.} = object of RootObj diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 71babe93b..b72face91 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -10,12 +10,9 @@ ## :Author: Alexander Mitchell-Robinson (Amrykid) ## ## This module implements operations for the built-in `seq`:idx: type which -## were inspired by functional programming languages. If you are looking for -## the typical `map` function which applies a function to every element in a -## sequence, it already exists in the `system <system.html>`_ module in both -## mutable and immutable styles. +## were inspired by functional programming languages. ## -## Also, for functional style programming you may want to pass `anonymous procs +## For functional style programming you may want to pass `anonymous procs ## <manual.html#anonymous-procs>`_ to procs like ``filter`` to reduce typing. ## Anonymous procs can use `the special do notation <manual.html#do-notation>`_ ## which is more convenient in certain situations. @@ -471,7 +468,7 @@ template toSeq*(iter: expr): expr {.immediate.} = ## if x mod 2 == 1: ## result = true) ## assert odd_numbers == @[1, 3, 5, 7, 9] - + when compiles(iter.len): var i = 0 var result = newSeq[type(iter)](iter.len) diff --git a/lib/pure/fsmonitor.nim b/lib/pure/fsmonitor.nim index 787acb5d4..115c4739e 100644 --- a/lib/pure/fsmonitor.nim +++ b/lib/pure/fsmonitor.nim @@ -34,8 +34,8 @@ type MonitorEventType* = enum ## Monitor event type MonitorAccess, ## File was accessed. MonitorAttrib, ## Metadata changed. - MonitorCloseWrite, ## Writtable file was closed. - MonitorCloseNoWrite, ## Unwrittable file closed. + MonitorCloseWrite, ## Writable file was closed. + MonitorCloseNoWrite, ## Non-writable file closed. MonitorCreate, ## Subfile was created. MonitorDelete, ## Subfile was deleted. MonitorDeleteSelf, ## Watched file/directory was itself deleted. @@ -78,21 +78,21 @@ proc add*(monitor: FSMonitor, target: string, ## watched paths of ``monitor``. ## You can specify the events to report using the ``filters`` parameter. - var INFilter = -1 + var INFilter = 0 for f in filters: case f - of MonitorAccess: INFilter = INFilter and IN_ACCESS - of MonitorAttrib: INFilter = INFilter and IN_ATTRIB - of MonitorCloseWrite: INFilter = INFilter and IN_CLOSE_WRITE - of MonitorCloseNoWrite: INFilter = INFilter and IN_CLOSE_NO_WRITE - of MonitorCreate: INFilter = INFilter and IN_CREATE - of MonitorDelete: INFilter = INFilter and IN_DELETE - of MonitorDeleteSelf: INFilter = INFilter and IN_DELETE_SELF - of MonitorModify: INFilter = INFilter and IN_MODIFY - of MonitorMoveSelf: INFilter = INFilter and IN_MOVE_SELF - of MonitorMoved: INFilter = INFilter and IN_MOVED_FROM and IN_MOVED_TO - of MonitorOpen: INFilter = INFilter and IN_OPEN - of MonitorAll: INFilter = INFilter and IN_ALL_EVENTS + of MonitorAccess: INFilter = INFilter or IN_ACCESS + of MonitorAttrib: INFilter = INFilter or IN_ATTRIB + of MonitorCloseWrite: INFilter = INFilter or IN_CLOSE_WRITE + of MonitorCloseNoWrite: INFilter = INFilter or IN_CLOSE_NO_WRITE + of MonitorCreate: INFilter = INFilter or IN_CREATE + of MonitorDelete: INFilter = INFilter or IN_DELETE + of MonitorDeleteSelf: INFilter = INFilter or IN_DELETE_SELF + of MonitorModify: INFilter = INFilter or IN_MODIFY + of MonitorMoveSelf: INFilter = INFilter or IN_MOVE_SELF + of MonitorMoved: INFilter = INFilter or IN_MOVED_FROM or IN_MOVED_TO + of MonitorOpen: INFilter = INFilter or IN_OPEN + of MonitorAll: INFilter = INFilter or IN_ALL_EVENTS result = inotifyAddWatch(monitor.fd, target, INFilter.uint32) if result < 0: @@ -200,9 +200,18 @@ proc register*(d: Dispatcher, monitor: FSMonitor, when not defined(testing) and isMainModule: proc main = - var disp = newDispatcher() - var monitor = newMonitor() - echo monitor.add("/home/dom/inotifytests/") + var + disp = newDispatcher() + monitor = newMonitor() + n = 0 + n = monitor.add("/tmp") + assert n == 1 + n = monitor.add("/tmp", {MonitorAll}) + assert n == 1 + n = monitor.add("/tmp", {MonitorCloseWrite, MonitorCloseNoWrite}) + assert n == 1 + n = monitor.add("/tmp", {MonitorMoved, MonitorOpen, MonitorAccess}) + assert n == 1 disp.register(monitor, proc (m: FSMonitor, ev: MonitorEvent) = echo("Got event: ", ev.kind) diff --git a/lib/pure/options.nim b/lib/pure/options.nim index 3122d58b1..2abb80016 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -28,7 +28,7 @@ ## ## .. code-block:: nim ## -## import optionals +## import options ## ## proc find(haystack: string, needle: char): Option[int] = ## for i, c in haystack: @@ -156,7 +156,7 @@ proc `$`*[T]( self: Option[T] ): string = when isMainModule: import unittest, sequtils - suite "optionals": + suite "options": # work around a bug in unittest let intNone = none(int) let stringNone = none(string) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 7c14c46cf..1e00f92b1 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1361,7 +1361,7 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = # /proc/<pid>/file when defined(windows): when useWinUnicode: - var buf = cast[WideCString](alloc(256*2)) + var buf = newWideCString("", 256) var len = getModuleFileNameW(0, buf, 256) result = buf$len else: diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 6c561eaf9..aa29bb073 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1344,8 +1344,8 @@ proc editDistance*(a, b: string): int {.noSideEffect, # floating point formating: - -proc c_sprintf(buf, frmt: cstring): cint {.header: "<stdio.h>", +when not defined(js): + proc c_sprintf(buf, frmt: cstring): cint {.header: "<stdio.h>", importc: "sprintf", varargs, noSideEffect.} type @@ -1370,29 +1370,44 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, ## after the decimal point for Nim's ``biggestFloat`` type. ## ## If ``precision == 0``, it tries to format it nicely. - const floatFormatToChar: array[FloatFormatMode, char] = ['g', 'f', 'e'] - var - frmtstr {.noinit.}: array[0..5, char] - buf {.noinit.}: array[0..2500, char] - L: cint - frmtstr[0] = '%' - if precision > 0: - frmtstr[1] = '#' - frmtstr[2] = '.' - frmtstr[3] = '*' - frmtstr[4] = floatFormatToChar[format] - frmtstr[5] = '\0' - L = c_sprintf(buf, frmtstr, precision, f) + when defined(js): + var res: cstring + case format + of ffDefault: + {.emit: "`res` = `f`.toString();".} + of ffDecimal: + {.emit: "`res` = `f`.toFixed(`precision`);".} + of ffScientific: + {.emit: "`res` = `f`.toExponential(`precision`);".} + result = $res + for i in 0 ..< result.len: + # Depending on the locale either dot or comma is produced, + # but nothing else is possible: + if result[i] in {'.', ','}: result[i] = decimalsep else: - frmtstr[1] = floatFormatToChar[format] - frmtstr[2] = '\0' - L = c_sprintf(buf, frmtstr, f) - result = newString(L) - for i in 0 ..< L: - # Depending on the locale either dot or comma is produced, - # but nothing else is possible: - if buf[i] in {'.', ','}: result[i] = decimalsep - else: result[i] = buf[i] + const floatFormatToChar: array[FloatFormatMode, char] = ['g', 'f', 'e'] + var + frmtstr {.noinit.}: array[0..5, char] + buf {.noinit.}: array[0..2500, char] + L: cint + frmtstr[0] = '%' + if precision > 0: + frmtstr[1] = '#' + frmtstr[2] = '.' + frmtstr[3] = '*' + frmtstr[4] = floatFormatToChar[format] + frmtstr[5] = '\0' + L = c_sprintf(buf, frmtstr, precision, f) + else: + frmtstr[1] = floatFormatToChar[format] + frmtstr[2] = '\0' + L = c_sprintf(buf, frmtstr, f) + result = newString(L) + for i in 0 ..< L: + # Depending on the locale either dot or comma is produced, + # but nothing else is possible: + if buf[i] in {'.', ','}: result[i] = decimalsep + else: result[i] = buf[i] proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, precision: range[0..32] = 16; decimalSep = '.'): string {. diff --git a/lib/pure/times.nim b/lib/pure/times.nim index a478b9d65..c9854a650 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1192,112 +1192,10 @@ proc timeToTimeInterval*(t: Time): TimeInterval = # Milliseconds not available from Time when isMainModule: - # $ date --date='@2147483647' - # Tue 19 Jan 03:14:07 GMT 2038 - - var t = getGMTime(fromSeconds(2147483647)) - assert t.format("ddd dd MMM hh:mm:ss ZZZ yyyy") == "Tue 19 Jan 03:14:07 UTC 2038" - assert t.format("ddd ddMMMhh:mm:ssZZZyyyy") == "Tue 19Jan03:14:07UTC2038" - - assert t.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == - "19 19 Tue Tuesday 3 03 3 03 14 14 1 01 Jan January 7 07 A AM 8 38 038 2038 02038 0 00 00:00 UTC" - - assert t.format("yyyyMMddhhmmss") == "20380119031407" - - var t2 = getGMTime(fromSeconds(160070789)) # Mon 27 Jan 16:06:29 GMT 1975 - assert t2.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == - "27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 0 00 00:00 UTC" - - when not defined(JS): - when sizeof(Time) == 8: - var t3 = getGMTime(fromSeconds(889067643645)) # Fri 7 Jun 19:20:45 BST 30143 - assert t3.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == - "7 07 Fri Friday 6 06 18 18 20 20 6 06 Jun June 45 45 P PM 3 43 143 0143 30143 0 00 00:00 UTC" - assert t3.format(":,[]()-/") == ":,[]()-/" - - var t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 - assert t4.format("M MM MMM MMMM") == "10 10 Oct October" - - # Interval tests - assert((t4 - initInterval(years = 2)).format("yyyy") == "1995") - assert((t4 - initInterval(years = 7, minutes = 34, seconds = 24)).format("yyyy mm ss") == "1990 24 10") - - var s = "Tuesday at 09:04am on Dec 15, 2015" - var f = "dddd at hh:mmtt on MMM d, yyyy" - assert($s.parse(f) == "Tue Dec 15 09:04:00 2015") - # ANSIC = "Mon Jan _2 15:04:05 2006" - s = "Thu Jan 12 15:04:05 2006" - f = "ddd MMM dd HH:mm:ss yyyy" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # UnixDate = "Mon Jan _2 15:04:05 MST 2006" - s = "Thu Jan 12 15:04:05 MST 2006" - f = "ddd MMM dd HH:mm:ss ZZZ yyyy" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # RubyDate = "Mon Jan 02 15:04:05 -0700 2006" - s = "Thu Jan 12 15:04:05 -07:00 2006" - f = "ddd MMM dd HH:mm:ss zzz yyyy" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # RFC822 = "02 Jan 06 15:04 MST" - s = "12 Jan 16 15:04 MST" - f = "dd MMM yy HH:mm ZZZ" - assert($s.parse(f) == "Tue Jan 12 15:04:00 2016") - # RFC822Z = "02 Jan 06 15:04 -0700" # RFC822 with numeric zone - s = "12 Jan 16 15:04 -07:00" - f = "dd MMM yy HH:mm zzz" - assert($s.parse(f) == "Tue Jan 12 15:04:00 2016") - # RFC850 = "Monday, 02-Jan-06 15:04:05 MST" - s = "Monday, 12-Jan-06 15:04:05 MST" - f = "dddd, dd-MMM-yy HH:mm:ss ZZZ" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" - s = "Thu, 12 Jan 2006 15:04:05 MST" - f = "ddd, dd MMM yyyy HH:mm:ss ZZZ" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" # RFC1123 with numeric zone - s = "Thu, 12 Jan 2006 15:04:05 -07:00" - f = "ddd, dd MMM yyyy HH:mm:ss zzz" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # RFC3339 = "2006-01-02T15:04:05Z07:00" - s = "2006-01-12T15:04:05Z-07:00" - f = "yyyy-MM-ddTHH:mm:ssZzzz" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - f = "yyyy-MM-dd'T'HH:mm:ss'Z'zzz" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" - s = "2006-01-12T15:04:05.999999999Z-07:00" - f = "yyyy-MM-ddTHH:mm:ss.999999999Zzzz" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # Kitchen = "3:04PM" - s = "3:04PM" - f = "h:mmtt" - assert "15:04:00" in $s.parse(f) - when not defined(testing): - echo "Kitchen: " & $s.parse(f) - var ti = timeToTimeInfo(getTime()) - echo "Todays date after decoding: ", ti - var tint = timeToTimeInterval(getTime()) - echo "Todays date after decoding to interval: ", tint - # checking dayOfWeek matches known days - assert getDayOfWeek(21, 9, 1900) == dFri - assert getDayOfWeek(1, 1, 1970) == dThu - assert getDayOfWeek(21, 9, 1970) == dMon - assert getDayOfWeek(1, 1, 2000) == dSat - assert getDayOfWeek(1, 1, 2021) == dFri - # Julian tests - assert getDayOfWeekJulian(21, 9, 1900) == dFri - assert getDayOfWeekJulian(21, 9, 1970) == dMon - assert getDayOfWeekJulian(1, 1, 2000) == dSat - assert getDayOfWeekJulian(1, 1, 2021) == dFri - - # toSeconds tests with GM and Local timezones - #var t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 - var t4L = getLocalTime(fromSeconds(876124714)) - assert toSeconds(timeInfoToTime(t4L)) == 876124714 # fromSeconds is effectively "localTime" - assert toSeconds(timeInfoToTime(t4L)) + t4L.timezone.float == toSeconds(timeInfoToTime(t4)) - + # this is testing non-exported function + var + t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 + t4L = getLocalTime(fromSeconds(876124714)) assert toSeconds(t4, initInterval(seconds=0)) == 0.0 assert toSeconds(t4L, initInterval(milliseconds=1)) == toSeconds(t4, initInterval(milliseconds=1)) assert toSeconds(t4L, initInterval(seconds=1)) == toSeconds(t4, initInterval(seconds=1)) @@ -1307,12 +1205,5 @@ when isMainModule: assert toSeconds(t4L, initInterval(months=1)) == toSeconds(t4, initInterval(months=1)) assert toSeconds(t4L, initInterval(years=1)) == toSeconds(t4, initInterval(years=1)) - # adding intervals - var - a1L = toSeconds(timeInfoToTime(t4L + initInterval(hours = 1))) + t4L.timezone.float - a1G = toSeconds(timeInfoToTime(t4)) + 60.0 * 60.0 - assert a1L == a1G - # subtracting intervals - a1L = toSeconds(timeInfoToTime(t4L - initInterval(hours = 1))) + t4L.timezone.float - a1G = toSeconds(timeInfoToTime(t4)) - (60.0 * 60.0) - assert a1L == a1G + # Further tests are in tests/stdlib/ttime.nim + # koch test c stdlib diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index b059a7315..45f52eb7f 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -114,6 +114,7 @@ proc validateUtf8*(s: string): int = if ord(s[i]) <=% 127: inc(i) elif ord(s[i]) shr 5 == 0b110: + if ord(s[i]) < 0xc2: return i # Catch overlong ascii representations. if i+1 < L and ord(s[i+1]) shr 6 == 0b10: inc(i, 2) else: return i elif ord(s[i]) shr 4 == 0b1110: diff --git a/lib/system.nim b/lib/system.nim index c5dd58c7b..ce7687c34 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -232,8 +232,8 @@ proc low*[T](x: T): T {.magic: "Low", noSideEffect.} ## ## .. code-block:: nim ## var arr = [1,2,3,4,5,6,7] - ## high(arr) #=> 0 - ## high(2) #=> -9223372036854775808 + ## low(arr) #=> 0 + ## low(2) #=> -9223372036854775808 type range*{.magic: "Range".}[T] ## Generic type to construct range types. @@ -2584,11 +2584,7 @@ when not defined(JS): #and not defined(nimscript): when hasAlloc: var - strDesc: TNimType - - strDesc.size = sizeof(string) - strDesc.kind = tyString - strDesc.flags = {ntfAcyclic} + strDesc = TNimType(size: sizeof(string), kind: tyString, flags: {ntfAcyclic}) when not defined(nimscript): include "system/ansi_c" diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index 3ebbc8c1e..b4462ed83 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -40,7 +40,7 @@ when defined(emscripten): MAP_PRIVATE = 2'i32 # Changes are private var MAP_ANONYMOUS {.importc: "MAP_ANONYMOUS", header: "<sys/mman.h>".}: cint - type + type PEmscriptenMMapBlock = ptr EmscriptenMMapBlock EmscriptenMMapBlock {.pure, inheritable.} = object realSize: int # size of previous chunk; for coalescing @@ -399,6 +399,9 @@ iterator allObjects(m: MemRegion): pointer {.inline.} = let c = cast[PBigChunk](c) yield addr(c.data) +proc iterToProc*(iter: typed, envType: typedesc; procName: untyped) {. + magic: "Plugin", compileTime.} + proc isCell(p: pointer): bool {.inline.} = result = cast[ptr FreeCell](p).zeroField >% 1 diff --git a/lib/system/cellsets.nim b/lib/system/cellsets.nim index bb5de6f42..776a2b7ec 100644 --- a/lib/system/cellsets.nim +++ b/lib/system/cellsets.nim @@ -201,6 +201,41 @@ iterator elements(t: CellSet): PCell {.inline.} = inc(i) r = r.next +when false: + type + CellSetIter = object + p: PPageDesc + i, w, j: int + + proc next(it: var CellSetIter): PCell = + while true: + while it.w != 0: # test all remaining bits for zero + if (it.w and 1) != 0: # the bit is set! + result = cast[PCell]((it.p.key shl PageShift) or + (it.i shl IntShift +% it.j) *% MemAlign) + + inc(it.j) + it.w = it.w shr 1 + return + else: + inc(it.j) + it.w = it.w shr 1 + # load next w: + if it.i >= high(it.p.bits): + it.i = 0 + it.j = 0 + it.p = it.p.next + if it.p == nil: return nil + else: + inc it.i + it.w = it.p.bits[i] + + proc init(it: var CellSetIter; t: CellSet): PCell = + it.p = t.head + it.i = -1 + it.w = 0 + result = it.next + iterator elementsExcept(t, s: CellSet): PCell {.inline.} = var r = t.head while r != nil: diff --git a/lib/system/dyncalls.nim b/lib/system/dyncalls.nim index 4043c8714..22ac613f8 100644 --- a/lib/system/dyncalls.nim +++ b/lib/system/dyncalls.nim @@ -68,7 +68,9 @@ when defined(posix): proc nimLoadLibrary(path: string): LibHandle = result = dlopen(path, RTLD_NOW) - #c_fprintf(c_stdout, "%s\n", dlerror()) + let error = dlerror() + if error != nil: + c_fprintf(c_stdout, "%s\n", error) proc nimGetProcAddr(lib: LibHandle, name: cstring): ProcAddr = result = dlsym(lib, name) diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 0c632aeb1..c25cf4606 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -558,7 +558,7 @@ proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer = # we split the old refcount in 2 parts. XXX This is still not entirely # correct if the pointer that receives growObj's result is on the stack. # A better fix would be to emit the location specific write barrier for - # 'growObj', but this is lost of more work and who knows what new problems + # 'growObj', but this is lots of more work and who knows what new problems # this would create. res.refcount = rcIncrement decRef(ol) diff --git a/lib/system/gc2.nim b/lib/system/gc2.nim index 4ca0d144f..e68a8586e 100644 --- a/lib/system/gc2.nim +++ b/lib/system/gc2.nim @@ -1,7 +1,7 @@ # # # Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -9,13 +9,15 @@ # Garbage Collector # -# The basic algorithm is *Deferrent Reference Counting* with cycle detection. -# This is achieved by combining a Deutsch-Bobrow garbage collector -# together with Christoper's partial mark-sweep garbage collector. -# -# Special care has been taken to avoid recursion as far as possible to avoid -# stack overflows when traversing deep datastructures. It is well-suited -# for soft real time applications (like games). +# The basic algorithm is *Deferred Reference Counting* with an incremental mark +# and sweep GC to free cycles. It is hard realtime in that if you play +# according to its rules, no deadline will ever be missed. + +# XXX Ensure by smart color masking that the object is not in the ZCT. + +when defined(nimCoroutines): + import arch + {.push profiler:off.} const @@ -29,82 +31,36 @@ const when withRealTime and not declared(getTicks): include "system/timers" when defined(memProfiler): - proc nimProfile(requestedSize: int) - -const - rcShift = 6 # the reference count is shifted so we can use - # the least significat bits for additinal flags: - - rcAlive = 0b00000 # object is reachable. - # color *black* in the original paper - - rcCycleCandidate = 0b00001 # possible root of a cycle. *purple* - - rcDecRefApplied = 0b00010 # the first dec-ref phase of the - # collector was already applied to this - # object. *gray* - - rcMaybeDead = 0b00011 # this object is a candidate for deletion - # during the collect cycles algorithm. - # *white*. - - rcReallyDead = 0b00100 # this is proved to be garbage - - rcRetiredBuffer = 0b00101 # this is a seq or string buffer that - # was replaced by a resize operation. - # see growObj for details + proc nimProfile(requestedSize: int) {.benign.} - rcColorMask = RefCount(0b00111) - - rcZct = 0b01000 # already added to ZCT - rcInCycleRoots = 0b10000 # already buffered as cycle candidate - rcHasStackRef = 0b100000 # the object had a stack ref in the last - # cycle collection - - rcMarkBit = rcHasStackRef # this is currently used for leak detection - # when traceGC is on - - rcBufferedAnywhere = rcZct or rcInCycleRoots +type + ObjectSpaceIter = object + state: range[-1..0] - rcIncrement = 1 shl rcShift # don't touch the color bits +iterToProc(allObjects, ptr ObjectSpaceIter, allObjectsAsProc) const - NewObjectsAreCycleRoots = true - # the alternative is to use the old strategy of adding cycle roots - # in incRef (in the compiler itself, this doesn't change much) - - IncRefRemovesCandidates = false - # this is safe only if we can reliably track the fact that the object - # has stack references. This could be easily done by adding another bit - # to the refcount field and setting it up in unmarkStackAndRegisters. - # The bit must also be set for new objects that are not rc1 and it must be - # examined in the decref loop in collectCycles. - # XXX: not implemented yet as tests didn't show any improvement from this - - MarkingSkipsAcyclicObjects = true - # Acyclic objects can be safely ignored in the mark and scan phases, - # because they cannot contribute to the internal count. - # XXX: if we generate specialized `markCyclic` and `markAcyclic` - # procs we can further optimize this as there won't be need for any - # checks in the code - - MinimumStackMarking = false - # Try to scan only the user stack and ignore the part of the stack - # belonging to the GC itself. see setStackTop for further info. - # XXX: still has problems in release mode in the compiler itself. - # investigate how it affects growObj - - CollectCyclesStats = false - + rcIncrement = 0b1000 # so that lowest 3 bits are not touched + rcBlackOrig = 0b000 + rcWhiteOrig = 0b001 + rcGrey = 0b010 # traditional color for incremental mark&sweep + rcUnused = 0b011 + ZctFlag = 0b100 # in ZCT + rcShift = 3 # shift by rcShift to get the reference counter + colorMask = 0b011 type WalkOp = enum - waPush + waMarkGlobal, # part of the backup mark&sweep + waMarkGrey, + waZctDecRef #, waDebug - Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall.} + Phase {.pure.} = enum + None, Marking, Sweeping + Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign.} # A ref type can have a finalizer that is called before the object's # storage is freed. - GcStat {.final, pure.} = object + GcStat = object stackScans: int # number of performed stack scans (for statistics) cycleCollections: int # number of performed full collections maxThreshold: int # max threshold that has been set @@ -113,134 +69,78 @@ type cycleTableSize: int # max entries in cycle table maxPause: int64 # max measured GC pause in nanoseconds - GcHeap {.final, pure.} = object # this contains the zero count and - # non-zero count table + GcStack = object + prev: ptr GcStack + next: ptr GcStack + starts: pointer + pos: pointer + maxStackSize: int + + GcHeap = object # this contains the zero count and + # non-zero count table + black: int # either 0 or 1. + stack: ptr GcStack stackBottom: pointer - stackTop: pointer + phase: Phase cycleThreshold: int + when useCellIds: + idGenerator: int zct: CellSeq # the zero count table decStack: CellSeq # cells in the stack that are to decref again - cycleRoots: CellSeq - tempStack: CellSeq # temporary stack for recursion elimination - freeStack: CellSeq # objects ready to be freed + greyStack: CellSeq recGcLock: int # prevent recursion via finalizers; no thread lock - cycleRootsTrimIdx: int # Trimming is a light-weight collection of the - # cycle roots table that uses a cheap linear scan - # to find only possitively dead objects. - # One strategy is to perform it only for new objects - # allocated between the invocations of collectZCT. - # This index indicates the start of the range of - # such new objects within the table. when withRealTime: maxPause: Nanos # max allowed pause in nanoseconds; active if > 0 region: MemRegion # garbage collected region stat: GcStat -{.deprecated: [TWalkOp: WalkOp, TFinalizer: Finalizer, TGcStat: GcStat, - TGcHeap: GcHeap].} + additionalRoots: CellSeq # dummy roots for GC_ref/unref + spaceIter: ObjectSpaceIter + var - gch* {.rtlThreadVar.}: GcHeap + gch {.rtlThreadVar.}: GcHeap when not defined(useNimRtl): instantiateForRegion(gch.region) -template acquire(gch: GcHeap) = - when hasThreadSupport and hasSharedHeap: - AcquireSys(HeapLock) - -template release(gch: GcHeap) = - when hasThreadSupport and hasSharedHeap: - releaseSys(HeapLock) - -template setColor(c: PCell, color) = - c.refcount = (c.refcount and not rcColorMask) or color - -template color(c: PCell): expr = - c.refcount and rcColorMask - -template isBitDown(c: PCell, bit): expr = - (c.refcount and bit) == 0 - -template isBitUp(c: PCell, bit): expr = - (c.refcount and bit) != 0 - -template setBit(c: PCell, bit): expr = - c.refcount = c.refcount or bit - -template isDead(c: Pcell): expr = - c.isBitUp(rcReallyDead) # also covers rcRetiredBuffer - -template clearBit(c: PCell, bit): expr = - c.refcount = c.refcount and (not RefCount(bit)) - -when debugGC: - var gcCollectionIdx = 0 - - proc colorStr(c: PCell): cstring = - let color = c.color - case color - of rcAlive: return "alive" - of rcMaybeDead: return "maybedead" - of rcCycleCandidate: return "candidate" - of rcDecRefApplied: return "marked" - of rcRetiredBuffer: return "retired" - of rcReallyDead: return "dead" - else: return "unknown?" - - proc inCycleRootsStr(c: PCell): cstring = - if c.isBitUp(rcInCycleRoots): result = "cycleroot" - else: result = "" - - proc inZctStr(c: PCell): cstring = - if c.isBitUp(rcZct): result = "zct" - else: result = "" - - proc writeCell*(msg: CString, c: PCell, force = false) = - var kind = -1 - if c.typ != nil: kind = ord(c.typ.kind) - when trackAllocationSource: - c_fprintf(c_stdout, "[GC %d] %s: %p %d rc=%ld %s %s %s from %s(%ld)\n", - gcCollectionIdx, - msg, c, kind, c.refcount shr rcShift, - c.colorStr, c.inCycleRootsStr, c.inZctStr, - c.filename, c.line) - else: - c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld\n", - msg, c, kind, c.refcount shr rcShift) - -proc addZCT(zct: var CellSeq, c: PCell) {.noinline.} = - if c.isBitDown(rcZct): - c.setBit rcZct - zct.add c - -template setStackTop(gch) = - # This must be called immediately after we enter the GC code - # to minimize the size of the scanned stack. The stack consumed - # by the GC procs may amount to 200-400 bytes depending on the - # build settings and this contributes to false-positives - # in the conservative stack marking - when MinimumStackMarking: - var stackTop {.volatile.}: pointer - gch.stackTop = addr(stackTop) - -template addCycleRoot(cycleRoots: var CellSeq, c: PCell) = - if c.color != rcCycleCandidate: - c.setColor rcCycleCandidate - - # the object may be buffered already. for example, consider: - # decref; incref; decref - if c.isBitDown(rcInCycleRoots): - c.setBit rcInCycleRoots - cycleRoots.add c +proc initGC() = + when not defined(useNimRtl): + when traceGC: + for i in low(CellState)..high(CellState): init(states[i]) + gch.cycleThreshold = InitialCycleThreshold + gch.stat.stackScans = 0 + gch.stat.cycleCollections = 0 + gch.stat.maxThreshold = 0 + gch.stat.maxStackSize = 0 + gch.stat.maxStackCells = 0 + gch.stat.cycleTableSize = 0 + # init the rt + init(gch.zct) + init(gch.decStack) + init(gch.additionalRoots) + init(gch.greyStack) + +template gcAssert(cond: bool, msg: string) = + when defined(useGcAssert): + if not cond: + echo "[GCASSERT] ", msg + GC_disable() + writeStackTrace() + quit 1 + +proc addZCT(s: var CellSeq, c: PCell) {.noinline.} = + if (c.refcount and ZctFlag) == 0: + c.refcount = c.refcount or ZctFlag + add(s, c) proc cellToUsr(cell: PCell): pointer {.inline.} = # convert object (=pointer to refcount) to pointer to userdata result = cast[pointer](cast[ByteAddress](cell)+%ByteAddress(sizeof(Cell))) -proc usrToCell*(usr: pointer): PCell {.inline.} = +proc usrToCell(usr: pointer): PCell {.inline.} = # convert pointer to userdata to object (=pointer to refcount) result = cast[PCell](cast[ByteAddress](usr)-%ByteAddress(sizeof(Cell))) -proc canbeCycleRoot(c: PCell): bool {.inline.} = +proc canBeCycleRoot(c: PCell): bool {.inline.} = result = ntfAcyclic notin c.typ.flags proc extGetCellType(c: pointer): PNimType {.compilerproc.} = @@ -254,14 +154,40 @@ proc internRefcount(p: pointer): int {.exportc: "getRefcount".} = when BitsPerPage mod (sizeof(int)*8) != 0: {.error: "(BitsPerPage mod BitsPerUnit) should be zero!".} +template color(c): expr = c.refCount and colorMask +template setColor(c, col) = + c.refcount = c.refcount and not colorMask or col + +proc writeCell(msg: cstring, c: PCell) = + var kind = -1 + if c.typ != nil: kind = ord(c.typ.kind) + when leakDetector: + c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld from %s(%ld)\n", + msg, c, kind, c.refcount shr rcShift, c.filename, c.line) + else: + c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld; color=%ld\n", + msg, c, kind, c.refcount shr rcShift, c.color) + +template gcTrace(cell, state: expr): stmt {.immediate.} = + when traceGC: traceCell(cell, state) + # forward declarations: -proc collectCT(gch: var GcHeap) -proc isOnStack*(p: pointer): bool {.noinline.} -proc forAllChildren(cell: PCell, op: WalkOp) -proc doOperation(p: pointer, op: WalkOp) -proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) +proc collectCT(gch: var GcHeap) {.benign.} +proc isOnStack(p: pointer): bool {.noinline, benign.} +proc forAllChildren(cell: PCell, op: WalkOp) {.benign.} +proc doOperation(p: pointer, op: WalkOp) {.benign.} +proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) {.benign.} # we need the prototype here for debugging purposes +when hasThreadSupport and hasSharedHeap: + template `--`(x: expr): expr = atomicDec(x, rcIncrement) <% rcIncrement + template `++`(x: expr): stmt = discard atomicInc(x, rcIncrement) +else: + template `--`(x: expr): expr = + dec(x, rcIncrement) + x <% rcIncrement + template `++`(x: expr): stmt = inc(x, rcIncrement) + proc prepareDealloc(cell: PCell) = if cell.typ.finalizer != nil: # the finalizer could invoke something that @@ -273,246 +199,127 @@ proc prepareDealloc(cell: PCell) = (cast[Finalizer](cell.typ.finalizer))(cellToUsr(cell)) dec(gch.recGcLock) -when traceGC: - # traceGC is a special switch to enable extensive debugging - type - CellState = enum - csAllocated, csFreed - {.deprecated: [TCellState: CellState].} - var - states: array[CellState, CellSet] - - proc traceCell(c: PCell, state: CellState) = - case state - of csAllocated: - if c in states[csAllocated]: - writeCell("attempt to alloc an already allocated cell", c) - sysAssert(false, "traceCell 1") - excl(states[csFreed], c) - # writecell("allocated", c) - of csFreed: - if c in states[csFreed]: - writeCell("attempt to free a cell twice", c) - sysAssert(false, "traceCell 2") - if c notin states[csAllocated]: - writeCell("attempt to free not an allocated cell", c) - sysAssert(false, "traceCell 3") - excl(states[csAllocated], c) - # writecell("freed", c) - incl(states[state], c) - - proc computeCellWeight(c: PCell): int = - var x: CellSet - x.init - - let startLen = gch.tempStack.len - c.forAllChildren waPush - - while startLen != gch.tempStack.len: - dec gch.tempStack.len - var c = gch.tempStack.d[gch.tempStack.len] - if c in states[csFreed]: continue - inc result - if c notin x: - x.incl c - c.forAllChildren waPush - - template markChildrenRec(cell) = - let startLen = gch.tempStack.len - cell.forAllChildren waPush - let isMarked = cell.isBitUp(rcMarkBit) - while startLen != gch.tempStack.len: - dec gch.tempStack.len - var c = gch.tempStack.d[gch.tempStack.len] - if c in states[csFreed]: continue - if c.isBitDown(rcMarkBit): - c.setBit rcMarkBit - c.forAllChildren waPush - if c.isBitUp(rcMarkBit) and not isMarked: - writecell("cyclic cell", cell) - cprintf "Weight %d\n", cell.computeCellWeight - - proc writeLeakage(onlyRoots: bool) = - if onlyRoots: - for c in elements(states[csAllocated]): - if c notin states[csFreed]: - markChildrenRec(c) - var f = 0 - var a = 0 - for c in elements(states[csAllocated]): - inc a - if c in states[csFreed]: inc f - elif c.isBitDown(rcMarkBit): - writeCell("leak", c) - cprintf "Weight %d\n", c.computeCellWeight - cfprintf(cstdout, "Allocations: %ld; freed: %ld\n", a, f) - -template gcTrace(cell, state: expr): stmt {.immediate.} = - when logGC: writeCell($state, cell) - when traceGC: traceCell(cell, state) - -template WithHeapLock(blk: stmt): stmt = - when hasThreadSupport and hasSharedHeap: AcquireSys(HeapLock) - blk - when hasThreadSupport and hasSharedHeap: ReleaseSys(HeapLock) - proc rtlAddCycleRoot(c: PCell) {.rtl, inl.} = # we MUST access gch as a global here, because this crosses DLL boundaries! - WithHeapLock: addCycleRoot(gch.cycleRoots, c) + discard proc rtlAddZCT(c: PCell) {.rtl, inl.} = # we MUST access gch as a global here, because this crosses DLL boundaries! - WithHeapLock: addZCT(gch.zct, c) + addZCT(gch.zct, c) -type - CyclicMode = enum - Cyclic, - Acyclic, - MaybeCyclic - - ReleaseType = enum - AddToZTC - FreeImmediately - - HeapType = enum - LocalHeap - SharedHeap -{.deprecated: [TCyclicMode: CyclicMode, TReleaseType: ReleaseType, - THeapType: HeapType].} - -template `++` (rc: RefCount, heapType: HeapType): stmt = - when heapType == SharedHeap: - discard atomicInc(rc, rcIncrement) - else: - inc rc, rcIncrement - -template `--`(rc: RefCount): expr = - dec rc, rcIncrement - rc <% rcIncrement - -template `--` (rc: RefCount, heapType: HeapType): expr = - (when heapType == SharedHeap: atomicDec(rc, rcIncrement) <% rcIncrement else: --rc) - -template doDecRef(cc: PCell, - heapType = LocalHeap, - cycleFlag = MaybeCyclic): stmt = - var c = cc - sysAssert(isAllocatedPtr(gch.region, c), "decRef: interiorPtr") - # XXX: move this elesewhere - - sysAssert(c.refcount >=% rcIncrement, "decRef") - if c.refcount--(heapType): - # this is the last reference from the heap - # add to a zero-count-table that will be matched against stack pointers +proc decRef(c: PCell) {.inline.} = + gcAssert(isAllocatedPtr(gch.region, c), "decRef: interiorPtr") + gcAssert(c.refcount >=% rcIncrement, "decRef") + if --c.refcount: rtlAddZCT(c) - else: - when cycleFlag != Acyclic: - if cycleFlag == Cyclic or canBeCycleRoot(c): - # a cycle may have been broken - rtlAddCycleRoot(c) - -template doIncRef(cc: PCell, - heapType = LocalHeap, - cycleFlag = MaybeCyclic): stmt = - var c = cc - c.refcount++(heapType) - when cycleFlag != Acyclic: - when NewObjectsAreCycleRoots: - if canbeCycleRoot(c): - addCycleRoot(gch.cycleRoots, c) - elif IncRefRemovesCandidates: - c.setColor rcAlive - # XXX: this is not really atomic enough! - -proc nimGCref(p: pointer) {.compilerProc, inline.} = doIncRef(usrToCell(p)) -proc nimGCunref(p: pointer) {.compilerProc, inline.} = doDecRef(usrToCell(p)) + +proc incRef(c: PCell) {.inline.} = + gcAssert(isAllocatedPtr(gch.region, c), "incRef: interiorPtr") + c.refcount = c.refcount +% rcIncrement + +proc nimGCref(p: pointer) {.compilerProc.} = + let cell = usrToCell(p) + incRef(cell) + add(gch.additionalRoots, cell) + +proc nimGCunref(p: pointer) {.compilerProc.} = + let cell = usrToCell(p) + decRef(cell) + var L = gch.additionalRoots.len-1 + var i = L + let d = gch.additionalRoots.d + while i >= 0: + if d[i] == cell: + d[i] = d[L] + dec gch.additionalRoots.len + break + dec(i) + +template markGrey(x: PCell) = + if x.color == 1-gch.black and gch.phase == Phase.Marking: + x.setColor(rcGrey) + add(gch.greyStack, x) + +proc GC_addCycleRoot*[T](p: ref T) {.inline.} = + ## adds 'p' to the cycle candidate set for the cycle collector. It is + ## necessary if you used the 'acyclic' pragma for optimization + ## purposes and need to break cycles manually. + rtlAddCycleRoot(usrToCell(cast[pointer](p))) proc nimGCunrefNoCycle(p: pointer) {.compilerProc, inline.} = sysAssert(allocInv(gch.region), "begin nimGCunrefNoCycle") var c = usrToCell(p) - sysAssert(isAllocatedPtr(gch.region, c), "nimGCunrefNoCycle: isAllocatedPtr") - if c.refcount--(LocalHeap): + gcAssert(isAllocatedPtr(gch.region, c), "nimGCunrefNoCycle: isAllocatedPtr") + if --c.refcount: rtlAddZCT(c) sysAssert(allocInv(gch.region), "end nimGCunrefNoCycle 2") sysAssert(allocInv(gch.region), "end nimGCunrefNoCycle 5") -template doAsgnRef(dest: PPointer, src: pointer, - heapType = LocalHeap, cycleFlag = MaybeCyclic): stmt = - sysAssert(not isOnStack(dest), "asgnRef") - # BUGFIX: first incRef then decRef! - if src != nil: doIncRef(usrToCell(src), heapType, cycleFlag) - if dest[] != nil: doDecRef(usrToCell(dest[]), heapType, cycleFlag) - dest[] = src - proc asgnRef(dest: PPointer, src: pointer) {.compilerProc, inline.} = # the code generator calls this proc! - doAsgnRef(dest, src, LocalHeap, MaybeCyclic) + gcAssert(not isOnStack(dest), "asgnRef") + # BUGFIX: first incRef then decRef! + if src != nil: + let s = usrToCell(src) + incRef(s) + markGrey(s) + if dest[] != nil: decRef(usrToCell(dest[])) + dest[] = src proc asgnRefNoCycle(dest: PPointer, src: pointer) {.compilerProc, inline.} = # the code generator calls this proc if it is known at compile time that no # cycle is possible. - doAsgnRef(dest, src, LocalHeap, Acyclic) + gcAssert(not isOnStack(dest), "asgnRefNoCycle") + if src != nil: + var c = usrToCell(src) + ++c.refcount + markGrey(c) + if dest[] != nil: + var c = usrToCell(dest[]) + if --c.refcount: + rtlAddZCT(c) + dest[] = src proc unsureAsgnRef(dest: PPointer, src: pointer) {.compilerProc.} = # unsureAsgnRef updates the reference counters only if dest is not on the # stack. It is used by the code generator if it cannot decide wether a # reference is in the stack or not (this can happen for var parameters). if not isOnStack(dest): - if src != nil: doIncRef(usrToCell(src)) - # XXX we must detect a shared heap here - # better idea may be to just eliminate the need for unsureAsgnRef - # + if src != nil: + let s = usrToCell(src) + incRef(s) + markGrey(s) # XXX finally use assembler for the stack checking instead! # the test for '!= nil' is correct, but I got tired of the segfaults # resulting from the crappy stack checking: - if cast[int](dest[]) >=% PageSize: doDecRef(usrToCell(dest[])) + if cast[int](dest[]) >=% PageSize: decRef(usrToCell(dest[])) else: # can't be an interior pointer if it's a stack location! - sysAssert(interiorAllocatedPtr(gch.region, dest)==nil, - "stack loc AND interior pointer") + gcAssert(interiorAllocatedPtr(gch.region, dest) == nil, + "stack loc AND interior pointer") dest[] = src -when hasThreadSupport and hasSharedHeap: - # shared heap version of the above procs - proc asgnRefSh(dest: PPointer, src: pointer) {.compilerProc, inline.} = - doAsgnRef(dest, src, SharedHeap, MaybeCyclic) - - proc asgnRefNoCycleSh(dest: PPointer, src: pointer) {.compilerProc, inline.} = - doAsgnRef(dest, src, SharedHeap, Acyclic) +type + GlobalMarkerProc = proc () {.nimcall, benign.} +var + globalMarkersLen: int + globalMarkers: array[0.. 7_000, GlobalMarkerProc] -proc initGC() = - when not defined(useNimRtl): - when traceGC: - for i in low(CellState)..high(CellState): init(states[i]) - gch.cycleThreshold = InitialCycleThreshold - gch.stat.stackScans = 0 - gch.stat.cycleCollections = 0 - gch.stat.maxThreshold = 0 - gch.stat.maxStackSize = 0 - gch.stat.maxStackCells = 0 - gch.stat.cycleTableSize = 0 - # init the rt - init(gch.zct) - init(gch.tempStack) - init(gch.freeStack) - init(gch.cycleRoots) - init(gch.decStack) +proc nimRegisterGlobalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} = + if globalMarkersLen <= high(globalMarkers): + globalMarkers[globalMarkersLen] = markerProc + inc globalMarkersLen + else: + echo "[GC] cannot register global variable; too many global variables" + quit 1 -proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) = +proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.benign.} = var d = cast[ByteAddress](dest) case n.kind of nkSlot: forAllChildrenAux(cast[pointer](d +% n.offset), n.typ, op) of nkList: for i in 0..n.len-1: - # inlined for speed - if n.sons[i].kind == nkSlot: - if n.sons[i].typ.kind in {tyRef, tyString, tySequence}: - doOperation(cast[PPointer](d +% n.sons[i].offset)[], op) - else: - forAllChildrenAux(cast[pointer](d +% n.sons[i].offset), - n.sons[i].typ, op) - else: - forAllSlotsAux(dest, n.sons[i], op) + forAllSlotsAux(dest, n.sons[i], op) of nkCase: var m = selectBranch(dest, n) if m != nil: forAllSlotsAux(dest, m, op) @@ -533,9 +340,10 @@ proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) = else: discard proc forAllChildren(cell: PCell, op: WalkOp) = - sysAssert(cell != nil, "forAllChildren: 1") - sysAssert(cell.typ != nil, "forAllChildren: 2") - sysAssert cell.typ.kind in {tyRef, tySequence, tyString}, "forAllChildren: 3" + gcAssert(cell != nil, "forAllChildren: 1") + gcAssert(isAllocatedPtr(gch.region, cell), "forAllChildren: 2") + gcAssert(cell.typ != nil, "forAllChildren: 3") + gcAssert cell.typ.kind in {tyRef, tySequence, tyString}, "forAllChildren: 4" let marker = cell.typ.marker if marker != nil: marker(cellToUsr(cell), op.int) @@ -547,10 +355,9 @@ proc forAllChildren(cell: PCell, op: WalkOp) = var d = cast[ByteAddress](cellToUsr(cell)) var s = cast[PGenericSeq](d) if s != nil: - let baseAddr = d +% GenericSeqSize for i in 0..s.len-1: - forAllChildrenAux(cast[pointer](baseAddr +% i *% cell.typ.base.size), - cell.typ.base, op) + forAllChildrenAux(cast[pointer](d +% i *% cell.typ.base.size +% + GenericSeqSize), cell.typ.base, op) else: discard proc addNewObjToZCT(res: PCell, gch: var GcHeap) {.inline.} = @@ -571,7 +378,7 @@ proc addNewObjToZCT(res: PCell, gch: var GcHeap) {.inline.} = template replaceZctEntry(i: expr) = c = d[i] if c.refcount >=% rcIncrement: - c.clearBit(rcZct) + c.refcount = c.refcount and not ZctFlag d[i] = res return if L > 8: @@ -592,408 +399,335 @@ proc addNewObjToZCT(res: PCell, gch: var GcHeap) {.inline.} = for i in countdown(L-1, max(0, L-8)): var c = d[i] if c.refcount >=% rcIncrement: - c.clearBit(rcZct) + c.refcount = c.refcount and not ZctFlag d[i] = res return add(gch.zct, res) -proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap, rc1 = false): pointer = +{.push stackTrace: off, profiler:off.} +proc gcInvariant*() = + sysAssert(allocInv(gch.region), "injected") + when declared(markForDebug): + markForDebug(gch) +{.pop.} + +proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer = # generates a new object and sets its reference counter to 0 - acquire(gch) sysAssert(allocInv(gch.region), "rawNewObj begin") - sysAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1") - + gcAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1") collectCT(gch) - sysAssert(allocInv(gch.region), "rawNewObj after collect") - var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell))) - sysAssert(allocInv(gch.region), "rawNewObj after rawAlloc") - - sysAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "newObj: 2") - + gcAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "newObj: 2") + # now it is buffered in the ZCT res.typ = typ - - when trackAllocationSource and not hasThreadSupport: - if framePtr != nil and framePtr.prev != nil and framePtr.prev.prev != nil: - res.filename = framePtr.prev.prev.filename - res.line = framePtr.prev.prev.line - else: - res.filename = "nofile" - - if rc1: - res.refcount = rcIncrement # refcount is 1 - else: - # its refcount is zero, so add it to the ZCT: - res.refcount = rcZct - addNewObjToZCT(res, gch) - - if NewObjectsAreCycleRoots and canBeCycleRoot(res): - res.setBit(rcInCycleRoots) - res.setColor rcCycleCandidate - gch.cycleRoots.add res - + when leakDetector and not hasThreadSupport: + if framePtr != nil and framePtr.prev != nil: + res.filename = framePtr.prev.filename + res.line = framePtr.prev.line + # refcount is zero, color is black, but mark it to be in the ZCT + res.refcount = ZctFlag or gch.black sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3") - + # its refcount is zero, so add it to the ZCT: + addNewObjToZCT(res, gch) when logGC: writeCell("new cell", res) gcTrace(res, csAllocated) - release(gch) + when useCellIds: + inc gch.idGenerator + res.id = gch.idGenerator result = cellToUsr(res) sysAssert(allocInv(gch.region), "rawNewObj end") {.pop.} -proc freeCell(gch: var GcHeap, c: PCell) = - # prepareDealloc(c) - gcTrace(c, csFreed) - - when reallyDealloc: rawDealloc(gch.region, c) - else: - sysAssert(c.typ != nil, "collectCycles") - zeroMem(c, sizeof(Cell)) - -template eraseAt(cells: var CellSeq, at: int): stmt = - cells.d[at] = cells.d[cells.len - 1] - dec cells.len - -template trimAt(roots: var CellSeq, at: int): stmt = - # This will remove a cycle root candidate during trimming. - # a candidate is removed either because it received a refup and - # it's no longer a candidate or because it received further refdowns - # and now it's dead for sure. - let c = roots.d[at] - c.clearBit(rcInCycleRoots) - roots.eraseAt(at) - if c.isBitUp(rcReallyDead) and c.refcount <% rcIncrement: - # This case covers both dead objects and retired buffers - # That's why we must also check the refcount (it may be - # kept possitive by stack references). - freeCell(gch, c) +proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl.} = + result = rawNewObj(typ, size, gch) + when defined(memProfiler): nimProfile(size) proc newObj(typ: PNimType, size: int): pointer {.compilerRtl.} = - setStackTop(gch) - result = rawNewObj(typ, size, gch, false) + result = rawNewObj(typ, size, gch) zeroMem(result, size) when defined(memProfiler): nimProfile(size) -proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl.} = - setStackTop(gch) - result = rawNewObj(typ, size, gch, false) - when defined(memProfiler): nimProfile(size) - proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} = - setStackTop(gch) # `newObj` already uses locks, so no need for them here. let size = addInt(mulInt(len, typ.base.size), GenericSeqSize) result = newObj(typ, size) cast[PGenericSeq](result).len = len cast[PGenericSeq](result).reserved = len + when defined(memProfiler): nimProfile(size) proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} = - setStackTop(gch) - result = rawNewObj(typ, size, gch, true) + # generates a new object and sets its reference counter to 1 + sysAssert(allocInv(gch.region), "newObjRC1 begin") + gcAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1") + collectCT(gch) + sysAssert(allocInv(gch.region), "newObjRC1 after collectCT") + + var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell))) + sysAssert(allocInv(gch.region), "newObjRC1 after rawAlloc") + sysAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "newObj: 2") + # now it is buffered in the ZCT + res.typ = typ + when leakDetector and not hasThreadSupport: + if framePtr != nil and framePtr.prev != nil: + res.filename = framePtr.prev.filename + res.line = framePtr.prev.line + res.refcount = rcIncrement or gch.black # refcount is 1 + sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3") + when logGC: writeCell("new cell", res) + gcTrace(res, csAllocated) + when useCellIds: + inc gch.idGenerator + res.id = gch.idGenerator + result = cellToUsr(res) + zeroMem(result, size) + sysAssert(allocInv(gch.region), "newObjRC1 end") when defined(memProfiler): nimProfile(size) proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} = - setStackTop(gch) let size = addInt(mulInt(len, typ.base.size), GenericSeqSize) result = newObjRC1(typ, size) cast[PGenericSeq](result).len = len cast[PGenericSeq](result).reserved = len + when defined(memProfiler): nimProfile(size) proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer = - acquire(gch) collectCT(gch) var ol = usrToCell(old) + gcAssert(isAllocatedPtr(gch.region, ol), "growObj: freed pointer?") + sysAssert(ol.typ != nil, "growObj: 1") - sysAssert(ol.typ.kind in {tyString, tySequence}, "growObj: 2") + gcAssert(ol.typ.kind in {tyString, tySequence}, "growObj: 2") sysAssert(allocInv(gch.region), "growObj begin") var res = cast[PCell](rawAlloc(gch.region, newsize + sizeof(Cell))) - var elemSize = if ol.typ.kind != tyString: ol.typ.base.size - else: 1 + var elemSize = 1 + if ol.typ.kind != tyString: elemSize = ol.typ.base.size - var oldsize = cast[PGenericSeq](old).len*elemSize + GenericSeqSize - - # XXX: This should happen outside - # call user-defined move code - # call user-defined default constructor + let oldsize = cast[PGenericSeq](old).len*elemSize + GenericSeqSize copyMem(res, ol, oldsize + sizeof(Cell)) - zeroMem(cast[pointer](cast[ByteAddress](res)+% oldsize +% sizeof(Cell)), + zeroMem(cast[pointer](cast[ByteAddress](res) +% oldsize +% sizeof(Cell)), newsize-oldsize) - sysAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "growObj: 3") - sysAssert(res.refcount shr rcShift <=% 1, "growObj: 4") - - when false: - if ol.isBitUp(rcZct): - var j = gch.zct.len-1 - var d = gch.zct.d - while j >= 0: - if d[j] == ol: - d[j] = res - break - dec(j) - - if ol.isBitUp(rcInCycleRoots): - for i in 0 .. <gch.cycleRoots.len: - if gch.cycleRoots.d[i] == ol: - eraseAt(gch.cycleRoots, i) - - freeCell(gch, ol) - - else: - # the new buffer inherits the GC state of the old one - if res.isBitUp(rcZct): gch.zct.add res - if res.isBitUp(rcInCycleRoots): gch.cycleRoots.add res - - # Pay attention to what's going on here! We're not releasing the old memory. - # This is because at this point there may be an interior pointer pointing - # into this buffer somewhere on the stack (due to `var` parameters now and - # and `let` and `var:var` stack locations in the future). - # We'll release the memory in the next GC cycle. If we release it here, - # we cannot guarantee that no memory will be corrupted when only safe - # language features are used. Accessing the memory after the seq/string - # has been invalidated may still result in logic errors in the user code. - # We may improve on that by protecting the page in debug builds or - # by providing a warning when we detect a stack pointer into it. - let bufferFlags = ol.refcount and rcBufferedAnywhere - if bufferFlags == 0: - # we need this in order to collect it safely later - ol.refcount = rcRetiredBuffer or rcZct - gch.zct.add ol - else: - ol.refcount = rcRetiredBuffer or bufferFlags - - when logGC: - writeCell("growObj old cell", ol) - writeCell("growObj new cell", res) - + # This can be wrong for intermediate temps that are nevertheless on the + # heap because of lambda lifting: + #gcAssert(res.refcount shr rcShift <=% 1, "growObj: 4") + when logGC: + writeCell("growObj old cell", ol) + writeCell("growObj new cell", res) + gcTrace(ol, csZctFreed) gcTrace(res, csAllocated) - release(gch) + when reallyDealloc: + sysAssert(allocInv(gch.region), "growObj before dealloc") + if ol.refcount shr rcShift <=% 1: + # free immediately to save space: + if (ol.refcount and ZctFlag) != 0: + var j = gch.zct.len-1 + var d = gch.zct.d + while j >= 0: + if d[j] == ol: + d[j] = res + break + dec(j) + rawDealloc(gch.region, ol) + else: + # we split the old refcount in 2 parts. XXX This is still not entirely + # correct if the pointer that receives growObj's result is on the stack. + # A better fix would be to emit the location specific write barrier for + # 'growObj', but this is lots of more work and who knows what new problems + # this would create. + res.refcount = rcIncrement or gch.black + decRef(ol) + else: + sysAssert(ol.typ != nil, "growObj: 5") + zeroMem(ol, sizeof(Cell)) + when useCellIds: + inc gch.idGenerator + res.id = gch.idGenerator result = cellToUsr(res) sysAssert(allocInv(gch.region), "growObj end") when defined(memProfiler): nimProfile(newsize-oldsize) proc growObj(old: pointer, newsize: int): pointer {.rtl.} = - setStackTop(gch) result = growObj(old, newsize, gch) {.push profiler:off.} -# ---------------- cycle collector ------------------------------------------- -proc doOperation(p: pointer, op: WalkOp) = - if p == nil: return - var c: PCell = usrToCell(p) - sysAssert(c != nil, "doOperation: 1") - gch.tempStack.add c +template takeStartTime(workPackageSize) {.dirty.} = + const workPackage = workPackageSize + var debugticker = 1000 + when withRealTime: + var steps = workPackage + var t0: Ticks + if gch.maxPause > 0: t0 = getticks() -proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} = - doOperation(d, WalkOp(op)) +template takeTime {.dirty.} = + when withRealTime: dec steps + dec debugticker + +template checkTime {.dirty.} = + if debugticker <= 0: + echo "in loop" + debugticker = 1000 + when withRealTime: + if steps == 0: + steps = workPackage + if gch.maxPause > 0: + let duration = getticks() - t0 + # the GC's measuring is not accurate and needs some cleanup actions + # (stack unmarking), so subtract some short amount of time in + # order to miss deadlines less often: + if duration >= gch.maxPause - 50_000: + return false -type - RecursionType = enum - FromChildren, - FromRoot -{.deprecated: [TRecursionType: RecursionType].} +# ---------------- cycle collector ------------------------------------------- -proc collectZCT(gch: var GcHeap): bool +proc freeCyclicCell(gch: var GcHeap, c: PCell) = + gcAssert(isAllocatedPtr(gch.region, c), "freeCyclicCell: freed pointer?") -template pseudoRecursion(typ: RecursionType, body: stmt): stmt = - discard + var d = gch.decStack.d + for i in 0..gch.decStack.len-1: + gcAssert d[i] != c, "wtf man, freeing obviously alive stuff?!!" + + prepareDealloc(c) + gcTrace(c, csCycFreed) + when logGC: writeCell("cycle collector dealloc cell", c) + when reallyDealloc: + sysAssert(allocInv(gch.region), "free cyclic cell") + rawDealloc(gch.region, c) + else: + gcAssert(c.typ != nil, "freeCyclicCell") + zeroMem(c, sizeof(Cell)) -proc trimCycleRoots(gch: var GcHeap, startIdx = gch.cycleRootsTrimIdx) = - var i = startIdx - while i < gch.cycleRoots.len: - if gch.cycleRoots.d[i].color != rcCycleCandidate: - gch.cycleRoots.trimAt i - else: - inc i +proc sweep(gch: var GcHeap): bool = + takeStartTime(100) + echo "loop start" + let black = gch.black + while true: + let x = allObjectsAsProc(gch.region, addr gch.spaceIter) + if gch.spaceIter.state < 0: break + takeTime() + if isCell(x): + # cast to PCell is correct here: + var c = cast[PCell](x) + gcAssert c.color != rcGrey, "cell is still grey?" + if c.color != black: freeCyclicCell(gch, c) + # Since this is incremental, we MUST not set the object to 'white' here. + # We could set all the remaining objects to white after the 'sweep' + # completed but instead we flip the meaning of black/white to save one + # traversal over the heap! + checkTime() + # prepare for next iteration: + echo "loop end" + gch.spaceIter = ObjectSpaceIter() + result = true - gch.cycleRootsTrimIdx = gch.cycleRoots.len +proc markRoot(gch: var GcHeap, c: PCell) = + # since we start with 'black' cells, we need to mark them here too: + if c.color != rcGrey: + c.setColor(rcGrey) + add(gch.greyStack, c) -# we now use a much simpler and non-recursive algorithm for cycle removal -proc collectCycles(gch: var GcHeap) = - if gch.cycleRoots.len == 0: return - gch.stat.cycleTableSize = max(gch.stat.cycleTableSize, gch.cycleRoots.len) +proc markIncremental(gch: var GcHeap): bool = + var L = addr(gch.greyStack.len) + takeStartTime(100) + while L[] > 0: + var c = gch.greyStack.d[0] + sysAssert(isAllocatedPtr(gch.region, c), "markIncremental: isAllocatedPtr") + gch.greyStack.d[0] = gch.greyStack.d[L[] - 1] + dec(L[]) + takeTime() + if c.color == rcGrey: + c.setColor(gch.black) + forAllChildren(c, waMarkGrey) + checkTime() + gcAssert gch.greyStack.len == 0, "markIncremental: greystack not empty " + result = true - when CollectCyclesStats: - let l0 = gch.cycleRoots.len - let tStart = getTicks() +proc markGlobals(gch: var GcHeap) = + for i in 0 .. < globalMarkersLen: globalMarkers[i]() +proc markLocals(gch: var GcHeap) = + var d = gch.decStack.d + for i in 0 .. < gch.decStack.len: + sysAssert isAllocatedPtr(gch.region, d[i]), "markLocals" + markRoot(gch, d[i]) + +when logGC: var - decrefs = 0 - increfs = 0 - collected = 0 - maybedeads = 0 - - template ignoreObject(c: PCell): expr = - # This controls which objects will be ignored in the mark and scan stages - (when MarkingSkipsAcyclicObjects: not canbeCycleRoot(c) else: false) - # not canbeCycleRoot(c) - # false - # c.isBitUp(rcHasStackRef) - - template earlyMarkAliveRec(cell) = - let startLen = gch.tempStack.len - cell.setColor rcAlive - cell.forAllChildren waPush - - while startLen != gch.tempStack.len: - dec gch.tempStack.len - var c = gch.tempStack.d[gch.tempStack.len] - if c.color != rcAlive: - c.setColor rcAlive - c.forAllChildren waPush - - template earlyMarkAlive(stackRoots) = - # This marks all objects reachable from the stack as alive before any - # of the other stages is executed. Such objects cannot be garbage and - # they don't need to participate in the recursive decref/incref. - for i in 0 .. <stackRoots.len: - var c = stackRoots.d[i] - # c.setBit rcHasStackRef - earlyMarkAliveRec(c) - - earlyMarkAlive(gch.decStack) - - when CollectCyclesStats: - let tAfterEarlyMarkAlive = getTicks() - - template recursiveDecRef(cell) = - let startLen = gch.tempStack.len - cell.setColor rcDecRefApplied - cell.forAllChildren waPush - - while startLen != gch.tempStack.len: - dec gch.tempStack.len - var c = gch.tempStack.d[gch.tempStack.len] - if ignoreObject(c): continue - - sysAssert(c.refcount >=% rcIncrement, "recursive dec ref") - dec c.refcount, rcIncrement - inc decrefs - if c.color != rcDecRefApplied: - c.setColor rcDecRefApplied - c.forAllChildren waPush - - template markRoots(roots) = - var i = 0 - while i < roots.len: - if roots.d[i].color == rcCycleCandidate: - recursiveDecRef(roots.d[i]) - inc i - else: - roots.trimAt i - - markRoots(gch.cycleRoots) - - when CollectCyclesStats: - let tAfterMark = getTicks() - c_printf "COLLECT CYCLES %d: %d/%d\n", gcCollectionIdx, gch.cycleRoots.len, l0 - - template recursiveMarkAlive(cell) = - let startLen = gch.tempStack.len - cell.setColor rcAlive - cell.forAllChildren waPush - - while startLen != gch.tempStack.len: - dec gch.tempStack.len - var c = gch.tempStack.d[gch.tempStack.len] - if ignoreObject(c): continue - inc c.refcount, rcIncrement - inc increfs - - if c.color != rcAlive: - c.setColor rcAlive - c.forAllChildren waPush - - template scanRoots(roots) = - for i in 0 .. <roots.len: - let startLen = gch.tempStack.len - gch.tempStack.add roots.d[i] - - while startLen != gch.tempStack.len: - dec gch.tempStack.len - var c = gch.tempStack.d[gch.tempStack.len] - if ignoreObject(c): continue - if c.color == rcDecRefApplied: - if c.refcount >=% rcIncrement: - recursiveMarkAlive(c) - else: - # note that this is not necessarily the ultimate - # destiny of the object. we may still mark it alive - # later if we encounter another node from where it's - # reachable. - c.setColor rcMaybeDead - inc maybedeads - c.forAllChildren waPush - - scanRoots(gch.cycleRoots) - - when CollectCyclesStats: - let tAfterScan = getTicks() - - template collectDead(roots) = - for i in 0 .. <roots.len: - var c = roots.d[i] - c.clearBit(rcInCycleRoots) - - let startLen = gch.tempStack.len - gch.tempStack.add c - - while startLen != gch.tempStack.len: - dec gch.tempStack.len - var c = gch.tempStack.d[gch.tempStack.len] - when MarkingSkipsAcyclicObjects: - if not canbeCycleRoot(c): - # This is an acyclic object reachable from a dead cyclic object - # We must do a normal decref here that may add the acyclic object - # to the ZCT - doDecRef(c, LocalHeap, Cyclic) - continue - if c.color == rcMaybeDead and not c.isBitUp(rcInCycleRoots): - c.setColor(rcReallyDead) - inc collected - c.forAllChildren waPush - # we need to postpone the actual deallocation in order to allow - # the finalizers to run while the data structures are still intact - gch.freeStack.add c - prepareDealloc(c) - - for i in 0 .. <gch.freeStack.len: - freeCell(gch, gch.freeStack.d[i]) - - collectDead(gch.cycleRoots) - - when CollectCyclesStats: - let tFinal = getTicks() - cprintf "times:\n early mark alive: %d ms\n mark: %d ms\n scan: %d ms\n collect: %d ms\n decrefs: %d\n increfs: %d\n marked dead: %d\n collected: %d\n", - (tAfterEarlyMarkAlive - tStart) div 1_000_000, - (tAfterMark - tAfterEarlyMarkAlive) div 1_000_000, - (tAfterScan - tAfterMark) div 1_000_000, - (tFinal - tAfterScan) div 1_000_000, - decrefs, - increfs, - maybedeads, - collected - - deinit(gch.cycleRoots) - init(gch.cycleRoots) - - deinit(gch.freeStack) - init(gch.freeStack) - - when MarkingSkipsAcyclicObjects: - # Collect the acyclic objects that became unreachable due to collected - # cyclic objects. - discard collectZCT(gch) - # collectZCT may add new cycle candidates and we may decide to loop here - # if gch.cycleRoots.len > 0: repeat - -var gcDebugging* = false - -var seqdbg* : proc (s: PGenericSeq) {.cdecl.} + cycleCheckA: array[100, PCell] + cycleCheckALen = 0 + + proc alreadySeen(c: PCell): bool = + for i in 0 .. <cycleCheckALen: + if cycleCheckA[i] == c: return true + if cycleCheckALen == len(cycleCheckA): + gcAssert(false, "cycle detection overflow") + quit 1 + cycleCheckA[cycleCheckALen] = c + inc cycleCheckALen + + proc debugGraph(s: PCell) = + if alreadySeen(s): + writeCell("child cell (already seen) ", s) + else: + writeCell("cell {", s) + forAllChildren(s, waDebug) + c_fprintf(c_stdout, "}\n") + +proc doOperation(p: pointer, op: WalkOp) = + if p == nil: return + var c: PCell = usrToCell(p) + gcAssert(c != nil, "doOperation: 1") + # the 'case' should be faster than function pointers because of easy + # prediction: + case op + of waZctDecRef: + #if not isAllocatedPtr(gch.region, c): + # c_fprintf(c_stdout, "[GC] decref bug: %p", c) + gcAssert(isAllocatedPtr(gch.region, c), "decRef: waZctDecRef") + gcAssert(c.refcount >=% rcIncrement, "doOperation 2") + #c.refcount = c.refcount -% rcIncrement + when logGC: writeCell("decref (from doOperation)", c) + decRef(c) + #if c.refcount <% rcIncrement: addZCT(gch.zct, c) + of waMarkGlobal: + when hasThreadSupport: + # could point to a cell which we don't own and don't want to touch/trace + if isAllocatedPtr(gch.region, c): + markRoot(gch, c) + else: + markRoot(gch, c) + of waMarkGrey: + if c.color == 1-gch.black: + c.setColor(rcGrey) + add(gch.greyStack, c) + #of waDebug: debugGraph(c) + +proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} = + doOperation(d, WalkOp(op)) + +proc collectZCT(gch: var GcHeap): bool {.benign.} + +proc collectCycles(gch: var GcHeap): bool = + # ensure the ZCT 'color' is not used: + while gch.zct.len > 0: discard collectZCT(gch) + case gch.phase + of Phase.None, Phase.Marking: + #if gch.phase == Phase.None: + gch.phase = Phase.Marking + markGlobals(gch) + markLocals(gch) + if markIncremental(gch): + gch.phase = Phase.Sweeping + of Phase.Sweeping: + gcAssert gch.greyStack.len == 0, "greystack not empty" + if sweep(gch): + gch.phase = Phase.None + # flip black/white meanings: + gch.black = 1 - gch.black + result = true proc gcMark(gch: var GcHeap, p: pointer) {.inline.} = # the addresses are not as cells on the stack, so turn them to cells: @@ -1005,235 +739,33 @@ proc gcMark(gch: var GcHeap, p: pointer) {.inline.} = var objStart = cast[PCell](interiorAllocatedPtr(gch.region, cell)) if objStart != nil: # mark the cell: - if objStart.color != rcReallyDead: - if gcDebugging: - # writeCell("marking ", objStart) - discard - else: - inc objStart.refcount, rcIncrement - gch.decStack.add objStart - else: - # With incremental clean-up, objects spend some time - # in various lists before being deallocated. - # We just found a reference on the stack to an object, - # which we have previously labeled as unreachable. - # This is either a bug in the GC or a pure accidental - # coincidence due to the conservative stack marking. - when debugGC: - # writeCell("marking dead object", objStart) - discard - when false: - if isAllocatedPtr(gch.region, cell): - sysAssert false, "allocated pointer but not interior?" - # mark the cell: - inc cell.refcount, rcIncrement - add(gch.decStack, cell) + objStart.refcount = objStart.refcount +% rcIncrement + add(gch.decStack, objStart) sysAssert(allocInv(gch.region), "gcMark end") -proc markThreadStacks(gch: var GcHeap) = - when hasThreadSupport and hasSharedHeap: - {.error: "not fully implemented".} - var it = threadList - while it != nil: - # mark registers: - for i in 0 .. high(it.registers): gcMark(gch, it.registers[i]) - var sp = cast[ByteAddress](it.stackBottom) - var max = cast[ByteAddress](it.stackTop) - # XXX stack direction? - # XXX unroll this loop: - while sp <=% max: - gcMark(gch, cast[PPointer](sp)[]) - sp = sp +% sizeof(pointer) - it = it.next - -# ----------------- stack management -------------------------------------- -# inspired from Smart Eiffel - -when defined(sparc): - const stackIncreases = false -elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or - defined(hp9000s700) or defined(hp9000s800) or defined(hp9000s820): - const stackIncreases = true -else: - const stackIncreases = false - -when not defined(useNimRtl): - {.push stack_trace: off.} - proc setStackBottom(theStackBottom: pointer) = - #c_fprintf(c_stdout, "stack bottom: %p;\n", theStackBottom) - # the first init must be the one that defines the stack bottom: - if gch.stackBottom == nil: gch.stackBottom = theStackBottom - else: - var a = cast[ByteAddress](theStackBottom) # and not PageMask - PageSize*2 - var b = cast[ByteAddress](gch.stackBottom) - #c_fprintf(c_stdout, "old: %p new: %p;\n",gch.stackBottom,theStackBottom) - when stackIncreases: - gch.stackBottom = cast[pointer](min(a, b)) - else: - gch.stackBottom = cast[pointer](max(a, b)) - {.pop.} - -proc stackSize(): int {.noinline.} = - var stackTop {.volatile.}: pointer - result = abs(cast[int](addr(stackTop)) - cast[int](gch.stackBottom)) +include gc_common -var - jmpbufSize {.importc: "sizeof(jmp_buf)", nodecl.}: int - # a little hack to get the size of a JmpBuf in the generated C code - # in a platform independent way - -when defined(sparc): # For SPARC architecture. - proc isOnStack(p: pointer): bool = - var stackTop {.volatile.}: pointer - stackTop = addr(stackTop) - var b = cast[ByteAddress](gch.stackBottom) - var a = cast[ByteAddress](stackTop) - var x = cast[ByteAddress](p) - result = a <=% x and x <=% b - - proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} = - when defined(sparcv9): - asm """"flushw \n" """ - else: - asm """"ta 0x3 ! ST_FLUSH_WINDOWS\n" """ - - var - max = gch.stackBottom - sp: PPointer - stackTop: array[0..1, pointer] - sp = addr(stackTop[0]) - # Addresses decrease as the stack grows. - while sp <= max: - gcMark(gch, sp[]) - sp = cast[PPointer](cast[ByteAddress](sp) +% sizeof(pointer)) - -elif defined(ELATE): - {.error: "stack marking code is to be written for this architecture".} - -elif stackIncreases: - # --------------------------------------------------------------------------- - # Generic code for architectures where addresses increase as the stack grows. - # --------------------------------------------------------------------------- - proc isOnStack(p: pointer): bool = - var stackTop {.volatile.}: pointer - stackTop = addr(stackTop) - var a = cast[ByteAddress](gch.stackBottom) - var b = cast[ByteAddress](stackTop) - var x = cast[ByteAddress](p) - result = a <=% x and x <=% b - - proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} = - var registers: C_JmpBuf - if c_setjmp(registers) == 0'i32: # To fill the C stack with registers. - var max = cast[ByteAddress](gch.stackBottom) - var sp = cast[ByteAddress](addr(registers)) +% jmpbufSize -% sizeof(pointer) - # sp will traverse the JMP_BUF as well (jmp_buf size is added, - # otherwise sp would be below the registers structure). - while sp >=% max: - gcMark(gch, cast[PPointer](sp)[]) - sp = sp -% sizeof(pointer) - -else: - # --------------------------------------------------------------------------- - # Generic code for architectures where addresses decrease as the stack grows. - # --------------------------------------------------------------------------- - proc isOnStack(p: pointer): bool = - var stackTop {.volatile.}: pointer - stackTop = addr(stackTop) - var b = cast[ByteAddress](gch.stackBottom) - var a = cast[ByteAddress](stackTop) - var x = cast[ByteAddress](p) - result = a <=% x and x <=% b - - proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} = - # We use a jmp_buf buffer that is in the C stack. - # Used to traverse the stack and registers assuming - # that 'setjmp' will save registers in the C stack. - type PStackSlice = ptr array [0..7, pointer] - var registers: C_JmpBuf - if c_setjmp(registers) == 0'i32: # To fill the C stack with registers. - when MinimumStackMarking: - # mark the registers - var jmpbufPtr = cast[ByteAddress](addr(registers)) - var jmpbufEnd = jmpbufPtr +% jmpbufSize - - while jmpbufPtr <=% jmpbufEnd: - gcMark(gch, cast[PPointer](jmpbufPtr)[]) - jmpbufPtr = jmpbufPtr +% sizeof(pointer) - - var sp = cast[ByteAddress](gch.stackTop) - else: - var sp = cast[ByteAddress](addr(registers)) - # mark the user stack - var max = cast[ByteAddress](gch.stackBottom) - # loop unrolled: - while sp <% max - 8*sizeof(pointer): - gcMark(gch, cast[PStackSlice](sp)[0]) - gcMark(gch, cast[PStackSlice](sp)[1]) - gcMark(gch, cast[PStackSlice](sp)[2]) - gcMark(gch, cast[PStackSlice](sp)[3]) - gcMark(gch, cast[PStackSlice](sp)[4]) - gcMark(gch, cast[PStackSlice](sp)[5]) - gcMark(gch, cast[PStackSlice](sp)[6]) - gcMark(gch, cast[PStackSlice](sp)[7]) - sp = sp +% sizeof(pointer)*8 - # last few entries: - while sp <=% max: - gcMark(gch, cast[PPointer](sp)[]) - sp = sp +% sizeof(pointer) - -# ---------------------------------------------------------------------------- -# end of non-portable code -# ---------------------------------------------------------------------------- - -proc releaseCell(gch: var GcHeap, cell: PCell) = - if cell.color != rcReallyDead: - prepareDealloc(cell) - cell.setColor rcReallyDead - - let l1 = gch.tempStack.len - cell.forAllChildren waPush - let l2 = gch.tempStack.len - for i in l1 .. <l2: - var cc = gch.tempStack.d[i] - if cc.refcount--(LocalHeap): - releaseCell(gch, cc) - else: - if canbeCycleRoot(cc): - addCycleRoot(gch.cycleRoots, cc) - - gch.tempStack.len = l1 - - if cell.isBitDown(rcBufferedAnywhere): - freeCell(gch, cell) - # else: - # This object is either buffered in the cycleRoots list and we'll leave - # it there to be collected in the next collectCycles or it's pending in - # the ZCT: - # (e.g. we are now cleaning the 15th object, but this one is 18th in the - # list. Note that this can happen only if we reached this point by the - # recursion). - # We can ignore it now as the ZCT cleaner will reach it soon. +proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} = + forEachStackSlot(gch, gcMark) proc collectZCT(gch: var GcHeap): bool = - const workPackage = 100 + # Note: Freeing may add child objects to the ZCT! So essentially we do + # deep freeing, which is bad for incremental operation. In order to + # avoid a deep stack, we move objects to keep the ZCT small. + # This is performance critical! var L = addr(gch.zct.len) - - when withRealtime: - var steps = workPackage - var t0: Ticks - if gch.maxPause > 0: t0 = getticks() + takeStartTime(100) while L[] > 0: var c = gch.zct.d[0] - sysAssert c.isBitUp(rcZct), "collectZCT: rcZct missing!" - sysAssert(isAllocatedPtr(gch.region, c), "collectZCT: isAllocatedPtr") - + sysAssert(isAllocatedPtr(gch.region, c), "CollectZCT: isAllocatedPtr") # remove from ZCT: - c.clearBit(rcZct) + gcAssert((c.refcount and ZctFlag) == ZctFlag, "collectZCT") + + c.refcount = c.refcount and not ZctFlag gch.zct.d[0] = gch.zct.d[L[] - 1] dec(L[]) - when withRealtime: dec steps + takeTime() if c.refcount <% rcIncrement: # It may have a RC > 0, if it is in the hardware stack or # it has not been removed yet from the ZCT. This is because @@ -1241,92 +773,78 @@ proc collectZCT(gch: var GcHeap): bool = # as this might be too slow. # In any case, it should be removed from the ZCT. But not # freed. **KEEP THIS IN MIND WHEN MAKING THIS INCREMENTAL!** - if c.color == rcRetiredBuffer: - if c.isBitDown(rcInCycleRoots): - freeCell(gch, c) + when logGC: writeCell("zct dealloc cell", c) + gcTrace(c, csZctFreed) + # We are about to free the object, call the finalizer BEFORE its + # children are deleted as well, because otherwise the finalizer may + # access invalid memory. This is done by prepareDealloc(): + prepareDealloc(c) + forAllChildren(c, waZctDecRef) + when reallyDealloc: + sysAssert(allocInv(gch.region), "collectZCT: rawDealloc") + rawDealloc(gch.region, c) else: - # if c.color == rcReallyDead: writeCell("ReallyDead in ZCT?", c) - releaseCell(gch, c) - when withRealtime: - if steps == 0: - steps = workPackage - if gch.maxPause > 0: - let duration = getticks() - t0 - # the GC's measuring is not accurate and needs some cleanup actions - # (stack unmarking), so subtract some short amount of time in to - # order to miss deadlines less often: - if duration >= gch.maxPause - 50_000: - return false + sysAssert(c.typ != nil, "collectZCT 2") + zeroMem(c, sizeof(Cell)) + checkTime() result = true - gch.trimCycleRoots - #deInit(gch.zct) - #init(gch.zct) proc unmarkStackAndRegisters(gch: var GcHeap) = var d = gch.decStack.d - for i in 0 .. <gch.decStack.len: + for i in 0..gch.decStack.len-1: sysAssert isAllocatedPtr(gch.region, d[i]), "unmarkStackAndRegisters" - # XXX: just call doDecRef? - var c = d[i] - sysAssert c.typ != nil, "unmarkStackAndRegisters 2" - - if c.color == rcRetiredBuffer: - continue - - # XXX no need for an atomic dec here: - if c.refcount--(LocalHeap): - # the object survived only because of a stack reference - # it still doesn't have heap references - addZCT(gch.zct, c) - - if canbeCycleRoot(c): - # any cyclic object reachable from the stack can be turned into - # a leak if it's orphaned through the stack reference - # that's because the write-barrier won't be executed for stack - # locations - addCycleRoot(gch.cycleRoots, c) - + decRef(d[i]) gch.decStack.len = 0 proc collectCTBody(gch: var GcHeap) = - when withRealtime: + when withRealTime: let t0 = getticks() - when debugGC: inc gcCollectionIdx sysAssert(allocInv(gch.region), "collectCT: begin") - gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize()) + when not defined(nimCoroutines): + gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize()) sysAssert(gch.decStack.len == 0, "collectCT") prepareForInteriorPointerChecking(gch.region) markStackAndRegisters(gch) - markThreadStacks(gch) gch.stat.maxStackCells = max(gch.stat.maxStackCells, gch.decStack.len) inc(gch.stat.stackScans) if collectZCT(gch): when cycleGC: if getOccupiedMem(gch.region) >= gch.cycleThreshold or alwaysCycleGC: - collectCycles(gch) - sysAssert gch.zct.len == 0, "zct is not null after collect cycles" - inc(gch.stat.cycleCollections) - gch.cycleThreshold = max(InitialCycleThreshold, getOccupiedMem() * - CycleIncrease) - gch.stat.maxThreshold = max(gch.stat.maxThreshold, gch.cycleThreshold) + if collectCycles(gch): + inc(gch.stat.cycleCollections) + gch.cycleThreshold = max(InitialCycleThreshold, getOccupiedMem() * + CycleIncrease) + gch.stat.maxThreshold = max(gch.stat.maxThreshold, gch.cycleThreshold) unmarkStackAndRegisters(gch) sysAssert(allocInv(gch.region), "collectCT: end") - when withRealtime: + when withRealTime: let duration = getticks() - t0 gch.stat.maxPause = max(gch.stat.maxPause, duration) when defined(reportMissedDeadlines): if gch.maxPause > 0 and duration > gch.maxPause: c_fprintf(c_stdout, "[GC] missed deadline: %ld\n", duration) +when defined(nimCoroutines): + proc currentStackSizes(): int = + for stack in items(gch.stack): + result = result + stackSize(stack.starts, stack.pos) + proc collectCT(gch: var GcHeap) = - if (gch.zct.len >= ZctThreshold or (cycleGC and + # stackMarkCosts prevents some pathological behaviour: Stack marking + # becomes more expensive with large stacks and large stacks mean that + # cells with RC=0 are more likely to be kept alive by the stack. + when defined(nimCoroutines): + let stackMarkCosts = max(currentStackSizes() div (16*sizeof(int)), ZctThreshold) + else: + let stackMarkCosts = max(stackSize() div (16*sizeof(int)), ZctThreshold) + if (gch.zct.len >= stackMarkCosts or (cycleGC and getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) and gch.recGcLock == 0: collectCTBody(gch) -when withRealtime: +when withRealTime: proc toNano(x: int): Nanos {.inline.} = result = x * 1000 @@ -1334,13 +852,11 @@ when withRealtime: gch.maxPause = MaxPauseInUs.toNano proc GC_step(gch: var GcHeap, us: int, strongAdvice: bool) = - acquire(gch) gch.maxPause = us.toNano if (gch.zct.len >= ZctThreshold or (cycleGC and getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) or strongAdvice: collectCTBody(gch) - release(gch) proc GC_step*(us: int, strongAdvice = false) = GC_step(gch, us, strongAdvice) @@ -1358,11 +874,7 @@ when not defined(useNimRtl): dec(gch.recGcLock) proc GC_setStrategy(strategy: GC_Strategy) = - case strategy - of gcThroughput: discard - of gcResponsiveness: discard - of gcOptimizeSpace: discard - of gcOptimizeTime: discard + discard proc GC_enableMarkAndSweep() = gch.cycleThreshold = InitialCycleThreshold @@ -1372,13 +884,10 @@ when not defined(useNimRtl): # set to the max value to suppress the cycle detector proc GC_fullCollect() = - setStackTop(gch) - acquire(gch) var oldThreshold = gch.cycleThreshold gch.cycleThreshold = 0 # forces cycle collection collectCT(gch) gch.cycleThreshold = oldThreshold - release(gch) proc GC_getStatistics(): string = GC_disable() @@ -1390,9 +899,13 @@ when not defined(useNimRtl): "[GC] max threshold: " & $gch.stat.maxThreshold & "\n" & "[GC] zct capacity: " & $gch.zct.cap & "\n" & "[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" & - "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" & "[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000) - when traceGC: writeLeakage(true) + when defined(nimCoroutines): + result = result & "[GC] number of stacks: " & $gch.stack.len & "\n" + for stack in items(gch.stack): + result = result & "[GC] stack " & stack.starts.repr & "[GC] max stack size " & $stack.maxStackSize & "\n" + else: + result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" GC_enable() {.pop.} diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 5bcddc5e6..54c6796c9 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -33,6 +33,9 @@ type lineNumber {.importc.}: int message {.importc.}: cstring stack {.importc.}: cstring + + JSRef = ref RootObj # Fake type. + {.deprecated: [TSafePoint: SafePoint, TCallFrame: CallFrame].} var @@ -282,61 +285,6 @@ proc eqStrings(a, b: string): bool {.asmNoStackFrame, compilerProc.} = return true; """ -type - Document {.importc.} = object of RootObj - write: proc (text: cstring) {.nimcall.} - writeln: proc (text: cstring) {.nimcall.} - createAttribute: proc (identifier: cstring): ref Node {.nimcall.} - createElement: proc (identifier: cstring): ref Node {.nimcall.} - createTextNode: proc (identifier: cstring): ref Node {.nimcall.} - getElementById: proc (id: cstring): ref Node {.nimcall.} - getElementsByName: proc (name: cstring): seq[ref Node] {.nimcall.} - getElementsByTagName: proc (name: cstring): seq[ref Node] {.nimcall.} - - NodeType* = enum - ElementNode = 1, - AttributeNode, - TextNode, - CDATANode, - EntityRefNode, - EntityNode, - ProcessingInstructionNode, - CommentNode, - DocumentNode, - DocumentTypeNode, - DocumentFragmentNode, - NotationNode - Node* {.importc.} = object of RootObj - attributes*: seq[ref Node] - childNodes*: seq[ref Node] - data*: cstring - firstChild*: ref Node - lastChild*: ref Node - nextSibling*: ref Node - nodeName*: cstring - nodeType*: NodeType - nodeValue*: cstring - parentNode*: ref Node - previousSibling*: ref Node - appendChild*: proc (child: ref Node) {.nimcall.} - appendData*: proc (data: cstring) {.nimcall.} - cloneNode*: proc (copyContent: bool) {.nimcall.} - deleteData*: proc (start, len: int) {.nimcall.} - getAttribute*: proc (attr: cstring): cstring {.nimcall.} - getAttributeNode*: proc (attr: cstring): ref Node {.nimcall.} - getElementsByTagName*: proc (): seq[ref Node] {.nimcall.} - hasChildNodes*: proc (): bool {.nimcall.} - insertBefore*: proc (newNode, before: ref Node) {.nimcall.} - insertData*: proc (position: int, data: cstring) {.nimcall.} - removeAttribute*: proc (attr: cstring) {.nimcall.} - removeAttributeNode*: proc (attr: ref Node) {.nimcall.} - removeChild*: proc (child: ref Node) {.nimcall.} - replaceChild*: proc (newNode, oldNode: ref Node) {.nimcall.} - replaceData*: proc (start, len: int, text: cstring) {.nimcall.} - setAttribute*: proc (name, value: cstring) {.nimcall.} - setAttributeNode*: proc (attr: ref Node) {.nimcall.} -{.deprecated: [TNode: Node, TNodeType: NodeType, TDocument: Document].} - when defined(kwin): proc rawEcho {.compilerproc, asmNoStackFrame.} = asm """ @@ -360,28 +308,28 @@ elif defined(nodejs): """ else: - var - document {.importc, nodecl.}: ref Document - proc ewriteln(x: cstring) = - var node = document.getElementsByTagName("body")[0] - if node != nil: - node.appendChild(document.createTextNode(x)) - node.appendChild(document.createElement("br")) - else: + var node : JSRef + {.emit: "`node` = document.getElementsByTagName('body')[0];".} + if node.isNil: raise newException(ValueError, "<body> element does not exist yet!") + {.emit: """ + `node`.appendChild(document.createTextNode(`x`)); + `node`.appendChild(document.createElement("br")); + """.} proc rawEcho {.compilerproc.} = - var node = document.getElementsByTagName("body")[0] - if node == nil: + var node : JSRef + {.emit: "`node` = document.getElementsByTagName('body')[0];".} + if node.isNil: raise newException(IOError, "<body> element does not exist yet!") - asm """ - for (var i = 0; i < arguments.length; ++i) { - var x = `toJSStr`(arguments[i]); - `node`.appendChild(document.createTextNode(x)) - } - """ - node.appendChild(document.createElement("br")) + {.emit: """ + for (var i = 0; i < arguments.length; ++i) { + var x = `toJSStr`(arguments[i]); + `node`.appendChild(document.createTextNode(x)); + } + `node`.appendChild(document.createElement("br")); + """.} # Arithmetic: proc addInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = @@ -532,8 +480,6 @@ proc nimMax(a, b: int): int {.compilerproc.} = return if a >= b: a else: b type NimString = string # hack for hti.nim include "system/hti" -type JSRef = ref RootObj # Fake type. - proc isFatPointer(ti: PNimType): bool = # This has to be consistent with the code generator! return ti.base.kind notin {tyObject, diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index 326c601bd..e2137e8f4 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -229,7 +229,7 @@ proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {. # we need to decref here, otherwise the GC leaks! when not defined(boehmGC) and not defined(nogc) and not defined(gcMarkAndSweep) and not defined(gogc): - when compileOption("gc", "v2"): + when false: # compileOption("gc", "v2"): for i in newLen..result.len-1: let len0 = gch.tempStack.len forAllChildrenAux(cast[pointer](cast[ByteAddress](result) +% diff --git a/tests/concepts/tmanual.nim b/tests/concepts/tmanual.nim index 7cf08af06..43290a6ad 100644 --- a/tests/concepts/tmanual.nim +++ b/tests/concepts/tmanual.nim @@ -23,7 +23,7 @@ template reject(e: expr) = type Container[T] = concept c c.len is Ordinal - items(c) is iterator + items(c) is T for value in c: type(value) is T diff --git a/tests/iter/titerable.nim b/tests/iter/titerable.nim index 3ec79f68d..fc1a8f934 100644 --- a/tests/iter/titerable.nim +++ b/tests/iter/titerable.nim @@ -6,8 +6,11 @@ discard """ 8 12 ''' + disabled: "true" """ +# Will eventually fix it... + iterator map[T, U](s: iterator:T{.inline.}, f: proc(x: T): U): U = for e in s: yield f(e) diff --git a/tests/metatype/ttypeclasses.nim b/tests/metatype/ttypeclasses.nim index db9db7713..720527088 100644 --- a/tests/metatype/ttypeclasses.nim +++ b/tests/metatype/ttypeclasses.nim @@ -17,12 +17,12 @@ type TFoo[T] = object val: T - T1 = expr - T2 = expr + T1 = auto + T2 = auto Numeric = int|float -proc takesExpr(x, y) = +proc takesExpr(x, y: auto) = echo x, y proc same(x, y: T1) = @@ -31,7 +31,7 @@ proc same(x, y: T1) = proc takesFoo(x, y: TFoo) = echo x.val, y.val -proc takes2Types(x,y: T1, z: T2) = +proc takes2Types[T1, T2](x,y: T1, z: T2) = echo x, y, z takesExpr(1, 2) diff --git a/tests/misc/tfsmonitor.nim b/tests/misc/tfsmonitor.nim new file mode 100644 index 000000000..27e1a2e32 --- /dev/null +++ b/tests/misc/tfsmonitor.nim @@ -0,0 +1,12 @@ +# +# fsmonitor test +# + +import unittest +import fsmonitor + +suite "fsmonitor": + test "should not raise OSError, bug# 3611": + let m = newMonitor() + m.add("foo", {MonitorCloseWrite, MonitorCloseNoWrite}) + diff --git a/tests/stdlib/ttime.nim b/tests/stdlib/ttime.nim index 859f0abdd..efc371995 100644 --- a/tests/stdlib/ttime.nim +++ b/tests/stdlib/ttime.nim @@ -1,6 +1,125 @@ # test the new time module +discard """ + file: "ttime.nim" +""" import - times + times, strutils -write(stdout, $getTime()) +assert( $getTime() == getLocalTime(getTime()).format("ddd MMM dd HH:mm:ss yyyy")) +# $ date --date='@2147483647' +# Tue 19 Jan 03:14:07 GMT 2038 + +var t = getGMTime(fromSeconds(2147483647)) +assert t.format("ddd dd MMM hh:mm:ss ZZZ yyyy") == "Tue 19 Jan 03:14:07 UTC 2038" +assert t.format("ddd ddMMMhh:mm:ssZZZyyyy") == "Tue 19Jan03:14:07UTC2038" + +assert t.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & + " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == + "19 19 Tue Tuesday 3 03 3 03 14 14 1 01 Jan January 7 07 A AM 8 38 038 2038 02038 0 00 00:00 UTC" + +assert t.format("yyyyMMddhhmmss") == "20380119031407" + +var t2 = getGMTime(fromSeconds(160070789)) # Mon 27 Jan 16:06:29 GMT 1975 +assert t2.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & + " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == + "27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 0 00 00:00 UTC" + +when not defined(JS): + when sizeof(Time) == 8: + var t3 = getGMTime(fromSeconds(889067643645)) # Fri 7 Jun 19:20:45 BST 30143 + assert t3.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & + " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == + "7 07 Fri Friday 6 06 18 18 20 20 6 06 Jun June 45 45 P PM 3 43 143 0143 30143 0 00 00:00 UTC" + assert t3.format(":,[]()-/") == ":,[]()-/" + +var t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 +assert t4.format("M MM MMM MMMM") == "10 10 Oct October" + +# Interval tests +assert((t4 - initInterval(years = 2)).format("yyyy") == "1995") +assert((t4 - initInterval(years = 7, minutes = 34, seconds = 24)).format("yyyy mm ss") == "1990 24 10") + +var s = "Tuesday at 09:04am on Dec 15, 2015" +var f = "dddd at hh:mmtt on MMM d, yyyy" +assert($s.parse(f) == "Tue Dec 15 09:04:00 2015") +# ANSIC = "Mon Jan _2 15:04:05 2006" +s = "Thu Jan 12 15:04:05 2006" +f = "ddd MMM dd HH:mm:ss yyyy" +assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") +# UnixDate = "Mon Jan _2 15:04:05 MST 2006" +s = "Thu Jan 12 15:04:05 MST 2006" +f = "ddd MMM dd HH:mm:ss ZZZ yyyy" +assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") +# RubyDate = "Mon Jan 02 15:04:05 -0700 2006" +s = "Thu Jan 12 15:04:05 -07:00 2006" +f = "ddd MMM dd HH:mm:ss zzz yyyy" +assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") +# RFC822 = "02 Jan 06 15:04 MST" +s = "12 Jan 16 15:04 MST" +f = "dd MMM yy HH:mm ZZZ" +assert($s.parse(f) == "Tue Jan 12 15:04:00 2016") +# RFC822Z = "02 Jan 06 15:04 -0700" # RFC822 with numeric zone +s = "12 Jan 16 15:04 -07:00" +f = "dd MMM yy HH:mm zzz" +assert($s.parse(f) == "Tue Jan 12 15:04:00 2016") +# RFC850 = "Monday, 02-Jan-06 15:04:05 MST" +s = "Monday, 12-Jan-06 15:04:05 MST" +f = "dddd, dd-MMM-yy HH:mm:ss ZZZ" +assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") +# RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" +s = "Thu, 12 Jan 2006 15:04:05 MST" +f = "ddd, dd MMM yyyy HH:mm:ss ZZZ" +assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") +# RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" # RFC1123 with numeric zone +s = "Thu, 12 Jan 2006 15:04:05 -07:00" +f = "ddd, dd MMM yyyy HH:mm:ss zzz" +assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") +# RFC3339 = "2006-01-02T15:04:05Z07:00" +s = "2006-01-12T15:04:05Z-07:00" +f = "yyyy-MM-ddTHH:mm:ssZzzz" +assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") +f = "yyyy-MM-dd'T'HH:mm:ss'Z'zzz" +assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") +# RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" +s = "2006-01-12T15:04:05.999999999Z-07:00" +f = "yyyy-MM-ddTHH:mm:ss.999999999Zzzz" +assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") +# Kitchen = "3:04PM" +s = "3:04PM" +f = "h:mmtt" +assert "15:04:00" in $s.parse(f) +#when not defined(testing): +# echo "Kitchen: " & $s.parse(f) +# var ti = timeToTimeInfo(getTime()) +# echo "Todays date after decoding: ", ti +# var tint = timeToTimeInterval(getTime()) +# echo "Todays date after decoding to interval: ", tint + +# checking dayOfWeek matches known days +assert getDayOfWeek(21, 9, 1900) == dFri +assert getDayOfWeek(1, 1, 1970) == dThu +assert getDayOfWeek(21, 9, 1970) == dMon +assert getDayOfWeek(1, 1, 2000) == dSat +assert getDayOfWeek(1, 1, 2021) == dFri +# Julian tests +assert getDayOfWeekJulian(21, 9, 1900) == dFri +assert getDayOfWeekJulian(21, 9, 1970) == dMon +assert getDayOfWeekJulian(1, 1, 2000) == dSat +assert getDayOfWeekJulian(1, 1, 2021) == dFri + +# toSeconds tests with GM and Local timezones +#var t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 +var t4L = getLocalTime(fromSeconds(876124714)) +assert toSeconds(timeInfoToTime(t4L)) == 876124714 # fromSeconds is effectively "localTime" +assert toSeconds(timeInfoToTime(t4L)) + t4L.timezone.float == toSeconds(timeInfoToTime(t4)) + +# adding intervals +var + a1L = toSeconds(timeInfoToTime(t4L + initInterval(hours = 1))) + t4L.timezone.float + a1G = toSeconds(timeInfoToTime(t4)) + 60.0 * 60.0 +assert a1L == a1G +# subtracting intervals +a1L = toSeconds(timeInfoToTime(t4L - initInterval(hours = 1))) + t4L.timezone.float +a1G = toSeconds(timeInfoToTime(t4)) - (60.0 * 60.0) +assert a1L == a1G diff --git a/tests/testament/categories.nim b/tests/testament/categories.nim index 762c92792..73d72289c 100644 --- a/tests/testament/categories.nim +++ b/tests/testament/categories.nim @@ -223,6 +223,9 @@ proc jsTests(r: var TResults, cat: Category, options: string) = "varres/tvartup"]: test "tests/" & testfile & ".nim" + for testfile in ["pure/strutils"]: + test "lib/" & testfile & ".nim" + # ------------------------- manyloc ------------------------------------------- #proc runSpecialTests(r: var TResults, options: string) = # for t in ["lib/packages/docutils/highlite"]: diff --git a/tests/types/tisop.nim b/tests/types/tisop.nim index d0b7c32b2..ad5928016 100644 --- a/tests/types/tisop.nim +++ b/tests/types/tisop.nim @@ -32,7 +32,7 @@ static: assert(f.y.type.name == "string") when compiles(f.z): {.error: "Foo should not have a `z` field".} -proc p(a, b) = +proc p(a, b: auto) = when a.type is int: static: assert false diff --git a/tests/types/tisopr.nim b/tests/types/tisopr.nim index b9acfa5fb..14999ebee 100644 --- a/tests/types/tisopr.nim +++ b/tests/types/tisopr.nim @@ -23,22 +23,23 @@ template yes(e: expr): stmt = template no(e: expr): stmt = static: assert(not e) -var s = @[1, 2, 3] +when false: + var s = @[1, 2, 3] -yes s.items is iterator -no s.items is proc + yes s.items is iterator + no s.items is proc -yes s.items is iterator: int -no s.items is iterator: float + yes s.items is iterator: int + no s.items is iterator: float -yes s.items is iterator: TNumber -no s.items is iterator: object + yes s.items is iterator: TNumber + no s.items is iterator: object -type - Iter[T] = iterator: T + type + Iter[T] = iterator: T -yes s.items is Iter[TNumber] -no s.items is Iter[float] + yes s.items is Iter[TNumber] + no s.items is Iter[float] type Foo[N: static[int], T] = object diff --git a/web/news.txt b/web/news.txt index 192ddbb74..67a7782cb 100644 --- a/web/news.txt +++ b/web/news.txt @@ -14,6 +14,11 @@ News - ``macros.newLit`` for ``bool`` now produces false/true symbols which actually work with the bool datatype. + - when compiling to JS, ``Node``, ``NodeType`` and ``Document`` are no longer + defined. Use the types defined in ``dom.nim`` instead. + - The check ``x is iterator`` (used for instance in concepts) was always a + weird special case (you could not use ``x is proc``) and was removed from + the language. 2015-10-27 Version 0.12.0 released diff --git a/web/website.ini b/web/website.ini index 9d7aab664..023262aa6 100644 --- a/web/website.ini +++ b/web/website.ini @@ -56,8 +56,8 @@ srcdoc2: "pure/memfiles;pure/subexes;pure/collections/critbits" srcdoc2: "deprecated/pure/asyncio;deprecated/pure/actors;core/locks;pure/oids;pure/endians;pure/uri" srcdoc2: "pure/nimprof;pure/unittest;packages/docutils/highlite" srcdoc2: "packages/docutils/rst;packages/docutils/rstast" -srcdoc2: "packages/docutils/rstgen;pure/logging;pure/asyncdispatch;pure/asyncnet" -srcdoc2: "deprecated/pure/rawsockets;pure/asynchttpserver;pure/net;pure/selectors;pure/future" +srcdoc2: "packages/docutils/rstgen;pure/logging;pure/options;pure/asyncdispatch;pure/asyncnet" +srcdoc2: "pure/nativesockets;pure/asynchttpserver;pure/net;pure/selectors;pure/future" srcdoc2: "deprecated/pure/ftpclient" srcdoc2: "pure/asyncfile;pure/asyncftpclient" srcdoc2: "pure/md5;pure/rationals" @@ -76,5 +76,3 @@ webdoc: "wrappers/libuv;wrappers/joyent_http_parser" webdoc: "posix/posix;wrappers/odbcsql" webdoc: "wrappers/libsvm.nim" - - |