diff --git a/changelog.md b/changelog.md index 8d59320672..665346ad70 100644 --- a/changelog.md +++ b/changelog.md @@ -33,7 +33,7 @@ errors. - Bitshift operators (`shl`, `shr`, `ashr`) now apply bitmasking to the right operand in the C/C++/VM/JS backends. -- Adds a new warning enabled by `--warning:ImplicitRangeConversion` that detects downsizing implicit conversions to range types (e.g., `int -> range[0..255]` or `range[1..256] -> range[0..255]`) that could cause runtime panics. Safe conversions like `range[0..255] -> range[0..65535]` and explicit casts are not warned on. +- Adds a new warning `--warning:ImplicitRangeConversion` that detects downsizing implicit conversions to range types (e.g., `int -> range[0..255]` or `range[1..256] -> range[0..255]`) that could cause runtime panics. Safe conversions like `range[0..255] -> range[0..65535]` and explicit casts do not trigger warnings. `int` to `Natural` and `Positive` conversions do not trigger warnings, which can be enabled with `--warning:systemRangeConversion`. ## Standard library additions and changes diff --git a/compiler/astdef.nim b/compiler/astdef.nim index b9a8aab3e1..242cdf3ef6 100644 --- a/compiler/astdef.nim +++ b/compiler/astdef.nim @@ -202,7 +202,11 @@ type tySequence, tyProc, tyPointer, tyOpenArray, - tyString, tyCstring, tyForward, + tyString, tyCstring, + tyForward, + # a type not yet semchecked + # When semcheck a type section, all types defined in it are initialized to tyForward + tyInt, tyInt8, tyInt16, tyInt32, tyInt64, # signed integers tyFloat, tyFloat32, tyFloat64, tyFloat128, tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64, diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index 6600561c9c..f0a5acc78c 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -1286,7 +1286,15 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp; tk = tyNone # no special casing for strings and seqs case tk of tySequence: + let needsYrcLock = g.config.selectedGC == gcYrc and + kind in {attachedDestructor, attachedSink, attachedAsgn, attachedDeepCopy, attachedDup} and + types.canFormAcycle(g, skipped.elementType) + # YRC: topology-changing seq ops must hold the mutator (read) lock + if needsYrcLock: + result.ast[bodyPos].add callCodegenProc(g, "acquireMutatorLock", info) fillSeqOp(a, typ, result.ast[bodyPos], d, src) + if needsYrcLock: + result.ast[bodyPos].add callCodegenProc(g, "releaseMutatorLock", info) of tyString: fillStrOp(a, typ, result.ast[bodyPos], d, src) else: diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index d9d44f277d..dc8708c3e4 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -99,6 +99,7 @@ type warnUser = "User", warnGlobalVarConstructorTemporary = "GlobalVarConstructorTemporary", warnImplicitRangeConversion = "ImplicitRangeConversion", + warnSystemRangeConversion = "SystemRangeConversion", # hints hintSuccess = "Success", hintSuccessX = "SuccessX", hintCC = "CC", @@ -208,6 +209,7 @@ const warnUser: "$1", warnGlobalVarConstructorTemporary: "global variable '$1' initialization requires a temporary variable", warnImplicitRangeConversion: "implicit range conversion $1", + warnSystemRangeConversion: "implicit range conversion $1", hintSuccess: "operation successful: $#", # keep in sync with `testament.isSuccess` hintSuccessX: "$build\n$loc lines; ${sec}s; $mem; proj: $project; out: $output", @@ -262,7 +264,7 @@ type proc computeNotesVerbosity(): array[0..3, TNoteKinds] = result = default(array[0..3, TNoteKinds]) - result[3] = {low(TNoteKind)..high(TNoteKind)} - {warnObservableStores, warnResultUsed, warnAnyEnumConv, warnBareExcept, warnStdPrefix, warnImplicitRangeConversion} + result[3] = {low(TNoteKind)..high(TNoteKind)} - {warnObservableStores, warnResultUsed, warnAnyEnumConv, warnBareExcept, warnStdPrefix, warnSystemRangeConversion} result[2] = result[3] - {hintStackTrace, hintExtendedContext, hintDeclaredLoc, hintProcessingStmt} result[1] = result[2] - {warnProveField, warnProveIndex, warnGcUnsafe, hintPath, hintDependency, hintCodeBegin, hintCodeEnd, diff --git a/compiler/nim.cfg b/compiler/nim.cfg index 9dab29eeed..425f0df324 100644 --- a/compiler/nim.cfg +++ b/compiler/nim.cfg @@ -65,3 +65,7 @@ define:useStdoutAsStdmsg @if nimHasVtables: experimental:vtables @end + +@if nimHasImplicitRangeConversion: + warning[ImplicitRangeConversion]:off +@end diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 8a91d820f0..87e085d4fd 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -236,6 +236,8 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym) let complexObj = containsGarbageCollectedRef(t) or hasDestructor(t) result = newIntNodeT(toInt128(ord(not complexObj)), traitCall, c.idgen, c.graph) + of "canFormCycles": + result = newIntNodeT(toInt128(ord(types.canFormAcycle(c.graph, operand))), traitCall, c.idgen, c.graph) of "hasDefaultValue": result = newIntNodeT(toInt128(ord(not operand.requiresInit)), traitCall, c.idgen, c.graph) of "isNamedTuple": diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index c206b7f075..c1279f3754 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -168,7 +168,7 @@ proc isRangeSupertype(conf: ConfigRef; wider, narrower: PType): bool = # int -> float ranges; warn result = false -proc shouldWarnRangeConversion(conf: ConfigRef; formalType, argType: PType): bool = +proc shouldWarnRangeConversion(conf: ConfigRef; info: TLineInfo; formalType, argType: PType): bool = ## Determine if an implicit range conversion should warn ## We warn on conversions that are likely to cause panics let f = formalType.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct}) @@ -176,7 +176,19 @@ proc shouldWarnRangeConversion(conf: ConfigRef; formalType, argType: PType): boo if f.kind == tyRange: # Only warn if formal range doesn't fully contain argument range # Check if the ranges don't perfectly overlap - result = not isRangeSupertype(conf, f, a) + if a.kind == tyInt and f.sym != nil and f.sym.owner != nil and + sfSystemModule in f.sym.owner.flags and + (f.sym.name.s == "Positive" or + f.sym.name.s == "Natural"): + # Positive and Natural are special cases that we do not warn on with + # ImplicitRangeConversion, but may warn on with systemRangeConversion + # if that warning is enabled. + if conf.hasWarn(warnSystemRangeConversion): + message(conf, info, warnSystemRangeConversion, + typeToString(argType) & " -> " & typeToString(formalType)) + result = false + else: + result = not isRangeSupertype(conf, f, a) else: result = false @@ -1538,7 +1550,7 @@ proc track(tracked: PEffects, n: PNode) = # Check for implicit range conversions if n.kind == nkHiddenStdConv and (not tracked.isArrayIndexing) and - shouldWarnRangeConversion(tracked.config, n.typ, n[1].typ): + shouldWarnRangeConversion(tracked.config, n.info, n.typ, n[1].typ): message(tracked.config, n.info, warnImplicitRangeConversion, typeToString(n[1].typ) & " -> " & typeToString(n.typ)) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index e87a8f760f..20cbd11911 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1745,6 +1745,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = var isConcrete = true let rType = m.call[0].typ let mIndex = if rType != nil: rType.len - 1 else: -1 + var hasForwardTypeParam = false for i in 1.. 0: let bound = result.typ.elementType.sym - if bound != nil: return bound + # the symbol may still point to the uninstantiated generic body type + if bound != nil and bound.typ == result.typ.elementType: + return bound return result if result.typ.sym == nil: localError(c.config, n.info, errTypeExpected) diff --git a/doc/manual.md b/doc/manual.md index f52e0ba38c..b0de4f58bb 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -1144,6 +1144,8 @@ semantic analysis). Assignments from the base type to one of its subrange types A subrange type has the same size as its base type (`int` in the Subrange example). +Implicit "downsizing" conversions to range types (for example, `int -> range[0..255]` or `range[1..256] -> range[0..255]`) emit the `ImplicitRangeConversion` warning. Conversions that are clearly safe (for example, `range[0..255] -> range[0..65535]`) and any explicit casts do not trigger this warning. Conversions from `int` to common subranges such as `Natural` or `Positive` do not trigger this warning by default, but can be enabled with `--warning:systemRangeConversion`. + Pre-defined floating-point types -------------------------------- diff --git a/koch.nim b/koch.nim index 1f193bce40..6f66a2ffe9 100644 --- a/koch.nim +++ b/koch.nim @@ -12,9 +12,9 @@ const # examples of possible values for repos: Head, ea82b54 NimbleStableCommit = "9207e8b2bbdf66b5a4d1020214cff44d2d30df92" # 0.20.1 - AtlasStableCommit = "2aa62121b40d580aa2fb27920a37b938d36c5f57" # 0.9.4 + AtlasStableCommit = "ff1f4289482dce94ba9f95b3b0ae16d16e21eb3d" # 0.10.1 ChecksumsStableCommit = "0b8e46379c5bc1bf73d8b3011908389c60fb9b98" # 2.0.1 - SatStableCommit = "faf1617f44d7632ee9601ebc13887644925dcc01" + SatStableCommit = "e63eaea8baf00bed8bcd5a29ffd8823abb265b39" NimonyStableCommit = "deb9b50c573fb55e071825ab55385e293b7216d5" # unversioned \ # Note that Nimony uses Nim as a git submodule but we don't want to install diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 605df443ec..793ae75a13 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -433,15 +433,15 @@ when defined(nimHasNoReturnError): else: {.pragma: errorNoReturn.} -proc error*(msg: string, n: NimNode = nil) {.magic: "NError", benign, errorNoReturn.} +proc error*(msg: string, n: NimNode = nil) {.magic: "NError", gcsafe, errorNoReturn.} ## Writes an error message at compile time. The optional `n: NimNode` ## parameter is used as the source for file and line number information in ## the compilation error message. -proc warning*(msg: string, n: NimNode = nil) {.magic: "NWarning", benign.} +proc warning*(msg: string, n: NimNode = nil) {.magic: "NWarning", gcsafe.} ## Writes a warning message at compile time. -proc hint*(msg: string, n: NimNode = nil) {.magic: "NHint", benign.} +proc hint*(msg: string, n: NimNode = nil) {.magic: "NHint", gcsafe.} ## Writes a hint message at compile time. proc newStrLitNode*(s: string): NimNode {.noSideEffect.} = @@ -511,7 +511,7 @@ proc genSym*(kind: NimSymKind = nskLet; ident = ""): NimNode {. ## Generates a fresh symbol that is guaranteed to be unique. The symbol ## needs to occur in a declaration context. -proc callsite*(): NimNode {.magic: "NCallSite", benign, deprecated: +proc callsite*(): NimNode {.magic: "NCallSite", gcsafe, deprecated: "Deprecated since v0.18.1; use `varargs[untyped]` in the macro prototype instead".} ## Returns the AST of the invocation expression that invoked this macro. # see https://github.com/nim-lang/RFCs/issues/387 as candidate replacement. @@ -933,7 +933,7 @@ proc eqIdent*(a: NimNode; b: NimNode): bool {.magic: "EqIdent", noSideEffect.} const collapseSymChoice = not defined(nimLegacyMacrosCollapseSymChoice) -proc treeTraverse(n: NimNode; res: var string; level = 0; isLisp = false, indented = false) {.benign.} = +proc treeTraverse(n: NimNode; res: var string; level = 0; isLisp = false, indented = false) {.gcsafe.} = if level > 0: if indented: res.add("\n") @@ -982,21 +982,21 @@ proc treeTraverse(n: NimNode; res: var string; level = 0; isLisp = false, indent if isLisp: res.add(")") -proc treeRepr*(n: NimNode): string {.benign.} = +proc treeRepr*(n: NimNode): string {.gcsafe.} = ## Convert the AST `n` to a human-readable tree-like string. ## ## See also `repr`, `lispRepr`_, and `astGenRepr`_. result = "" n.treeTraverse(result, isLisp = false, indented = true) -proc lispRepr*(n: NimNode; indented = false): string {.benign.} = +proc lispRepr*(n: NimNode; indented = false): string {.gcsafe.} = ## Convert the AST `n` to a human-readable lisp-like string. ## ## See also `repr`, `treeRepr`_, and `astGenRepr`_. result = "" n.treeTraverse(result, isLisp = true, indented = indented) -proc astGenRepr*(n: NimNode): string {.benign.} = +proc astGenRepr*(n: NimNode): string {.gcsafe.} = ## Convert the AST `n` to the code required to generate that AST. ## ## See also `repr`_, `treeRepr`_, and `lispRepr`_. @@ -1005,7 +1005,7 @@ proc astGenRepr*(n: NimNode): string {.benign.} = NodeKinds = {nnkEmpty, nnkIdent, nnkSym, nnkNone, nnkCommentStmt} LitKinds = {nnkCharLit..nnkInt64Lit, nnkFloatLit..nnkFloat64Lit, nnkStrLit..nnkTripleStrLit} - proc traverse(res: var string, level: int, n: NimNode) {.benign.} = + proc traverse(res: var string, level: int, n: NimNode) {.gcsafe.} = for i in 0..level-1: res.add " " if n.kind in NodeKinds: res.add("new" & ($n.kind).substr(3) & "Node(") diff --git a/lib/pure/base64.nim b/lib/pure/base64.nim index 3b8fb9b681..f238862508 100644 --- a/lib/pure/base64.nim +++ b/lib/pure/base64.nim @@ -255,6 +255,9 @@ proc decode*(s: string): string = while inputIndex <= inputEnds: while s[inputIndex] in {'\n', '\r', ' '}: inc inputIndex + # double check inputIndex as it can be incremented due to whitespace + if inputIndex > inputEnds: + break inputChar(a) inputChar(b) inputChar(c) diff --git a/lib/pure/random.nim b/lib/pure/random.nim index 21303fdb64..ec5fef9e4c 100644 --- a/lib/pure/random.nim +++ b/lib/pure/random.nim @@ -243,7 +243,7 @@ proc rand[T: uint | uint64](r: var Rand; max: T): T = else: inc iters -proc rand*(r: var Rand; max: Natural): int {.benign.} = +proc rand*(r: var Rand; max: Natural): int {.gcsafe.} = ## Returns a random integer in the range `0..max` using the given state. ## ## **See also:** @@ -260,7 +260,7 @@ proc rand*(r: var Rand; max: Natural): int {.benign.} = cast[int](rand(r, uint64(max))) # xxx toUnsigned pending https://github.com/nim-lang/Nim/pull/18445 -proc rand*(max: int): int {.benign.} = +proc rand*(max: int): int {.gcsafe.} = ## Returns a random integer in the range `0..max`. ## ## If `randomize <#randomize>`_ has not been called, the sequence of random @@ -281,7 +281,7 @@ proc rand*(max: int): int {.benign.} = rand(state, max) -proc rand*(r: var Rand; max: range[0.0 .. high(float)]): float {.benign.} = +proc rand*(r: var Rand; max: range[0.0 .. high(float)]): float {.gcsafe.} = ## Returns a random floating point number in the range `0.0..max` ## using the given state. ## @@ -308,7 +308,7 @@ proc rand*(r: var Rand; max: range[0.0 .. high(float)]): float {.benign.} = let u = (0x3FFu64 shl 52u64) or (x shr 12u64) result = (cast[float](u) - 1.0) * max -proc rand*(max: float): float {.benign.} = +proc rand*(max: float): float {.gcsafe.} = ## Returns a random floating point number in the range `0.0..max`. ## ## If `randomize <#randomize>`_ has not been called, the sequence of random @@ -612,7 +612,7 @@ proc initRand*(seed: int64): Rand = skipRandomNumbers(result) discard next(result) -proc randomize*(seed: int64) {.benign.} = +proc randomize*(seed: int64) {.gcsafe.} = ## Initializes the default random number generator with the given seed. ## ## Providing a specific seed will produce the same results for that seed each time. @@ -736,7 +736,7 @@ when not defined(standalone): since (1, 5, 1): export initRand - proc randomize*() {.benign.} = + proc randomize*() {.gcsafe.} = ## Initializes the default random number generator with a seed based on ## random number source. ## diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 3cdd3903c9..2951ac6cdb 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -382,9 +382,9 @@ type ## timezones. The `times` module only supplies implementations for the ## system's local time and UTC. zonedTimeFromTimeImpl: proc (x: Time): ZonedTime - {.tags: [], raises: [], benign.} + {.tags: [], raises: [], gcsafe.} zonedTimeFromAdjTimeImpl: proc (x: Time): ZonedTime - {.tags: [], raises: [], benign.} + {.tags: [], raises: [], gcsafe.} name: string ZonedTime* = object ## Represents a point in time with an associated @@ -432,7 +432,7 @@ else: # Helper procs # -{.pragma: operator, rtl, noSideEffect, benign.} +{.pragma: operator, rtl, noSideEffect, gcsafe.} proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T {.inline.} = @@ -518,7 +518,7 @@ proc fromEpochDay(epochday: int64): return (d.MonthdayRange, m.Month, (y + ord(m <= 2)).int) proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int): - YeardayRange {.tags: [], raises: [], benign.} = + YeardayRange {.tags: [], raises: [], gcsafe.} = ## Returns the day of the year. ## Equivalent with `dateTime(year, month, monthday, 0, 0, 0, 0).yearday`. runnableExamples: @@ -538,7 +538,7 @@ proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int): result = daysUntilMonth[month] + monthday - 1 proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay - {.tags: [], raises: [], benign.} = + {.tags: [], raises: [], gcsafe.} = ## Returns the day of the week enum from day, month and year. ## Equivalent with `dateTime(year, month, monthday, 0, 0, 0, 0).weekday`. runnableExamples: @@ -922,21 +922,21 @@ proc nanosecond*(time: Time): NanosecondRange = time.nanosecond proc fromUnix*(unix: int64): Time - {.benign, tags: [], raises: [], noSideEffect.} = + {.gcsafe, tags: [], raises: [], noSideEffect.} = ## Convert a unix timestamp (seconds since `1970-01-01T00:00:00Z`) ## to a `Time`. runnableExamples: doAssert $fromUnix(0).utc == "1970-01-01T00:00:00Z" initTime(unix, 0) -proc toUnix*(t: Time): int64 {.benign, tags: [], raises: [], noSideEffect.} = +proc toUnix*(t: Time): int64 {.gcsafe, tags: [], raises: [], noSideEffect.} = ## Convert `t` to a unix timestamp (seconds since `1970-01-01T00:00:00Z`). ## See also `toUnixFloat` for subsecond resolution. runnableExamples: doAssert fromUnix(0).toUnix() == 0 t.seconds -proc fromUnixFloat(seconds: float): Time {.benign, tags: [], raises: [], noSideEffect.} = +proc fromUnixFloat(seconds: float): Time {.gcsafe, tags: [], raises: [], noSideEffect.} = ## Convert a unix timestamp in seconds to a `Time`; same as `fromUnix` ## but with subsecond resolution. runnableExamples: @@ -946,7 +946,7 @@ proc fromUnixFloat(seconds: float): Time {.benign, tags: [], raises: [], noSideE let nsecs = (seconds - secs) * 1e9 initTime(secs.int64, nsecs.NanosecondRange) -proc toUnixFloat(t: Time): float {.benign, tags: [], raises: [].} = +proc toUnixFloat(t: Time): float {.gcsafe, tags: [], raises: [].} = ## Same as `toUnix` but using subsecond resolution. runnableExamples: let t = getTime() @@ -975,7 +975,7 @@ proc toWinTime*(t: Time): int64 = proc getTimeImpl(typ: typedesc[Time]): Time = raiseAssert "implemented in the vm" -proc getTime*(): Time {.tags: [TimeEffect], benign.} = +proc getTime*(): Time {.tags: [TimeEffect], gcsafe.} = ## Gets the current time as a `Time` with up to nanosecond resolution. when nimvm: result = getTimeImpl(Time) @@ -1154,7 +1154,7 @@ proc isLeapDay*(dt: DateTime): bool {.since: (1, 1).} = assertDateTimeInitialized dt dt.year.isLeapYear and dt.month == mFeb and dt.monthday == 29 -proc toTime*(dt: DateTime): Time {.tags: [], raises: [], benign.} = +proc toTime*(dt: DateTime): Time {.tags: [], raises: [], gcsafe.} = ## Converts a `DateTime` to a `Time` representing the same point in time. assertDateTimeInitialized dt let epochDay = toEpochDay(dt.monthday, dt.month, dt.year) @@ -1197,9 +1197,9 @@ proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime = proc newTimezone*( name: string, zonedTimeFromTimeImpl: proc (time: Time): ZonedTime - {.tags: [], raises: [], benign.}, + {.tags: [], raises: [], gcsafe.}, zonedTimeFromAdjTimeImpl: proc (adjTime: Time): ZonedTime - {.tags: [], raises: [], benign.} + {.tags: [], raises: [], gcsafe.} ): owned Timezone = ## Create a new `Timezone`. ## @@ -1263,12 +1263,12 @@ proc `==`*(zone1, zone2: Timezone): bool = zone1.name == zone2.name proc inZone*(time: Time, zone: Timezone): DateTime - {.tags: [], raises: [], benign.} = + {.tags: [], raises: [], gcsafe.} = ## Convert `time` into a `DateTime` using `zone` as the timezone. result = initDateTime(zone.zonedTimeFromTime(time), zone) proc inZone*(dt: DateTime, zone: Timezone): DateTime - {.tags: [], raises: [], benign.} = + {.tags: [], raises: [], gcsafe.} = ## Returns a `DateTime` representing the same point in time as `dt` but ## using `zone` as the timezone. assertDateTimeInitialized dt @@ -1283,14 +1283,14 @@ proc toAdjTime(dt: DateTime): Time = result = initTime(seconds, dt.nanosecond) when defined(js): - proc localZonedTimeFromTime(time: Time): ZonedTime {.benign.} = + proc localZonedTimeFromTime(time: Time): ZonedTime {.gcsafe.} = let jsDate = newDate(time.seconds * 1000) let offset = jsDate.getTimezoneOffset() * secondsInMin result.time = time result.utcOffset = offset result.isDst = false - proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.benign.} = + proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.gcsafe.} = let utcDate = newDate(adjTime.seconds * 1000) let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), utcDate.getUTCDate(), utcDate.getUTCHours(), utcDate.getUTCMinutes(), @@ -1337,11 +1337,11 @@ else: return ((a.int64 - tm.toAdjUnix).int, tm.tm_isdst > 0) return (0, false) - proc localZonedTimeFromTime(time: Time): ZonedTime {.benign.} = + proc localZonedTimeFromTime(time: Time): ZonedTime {.gcsafe.} = let (offset, dst) = getLocalOffsetAndDst(time.seconds) result = ZonedTime(time: time, utcOffset: offset, isDst: dst) - proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.benign.} = + proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.gcsafe.} = var adjUnix = adjTime.seconds let past = adjUnix - secondsInDay let (pastOffset, _) = getLocalOffsetAndDst(past) @@ -1408,7 +1408,7 @@ proc local*(t: Time): DateTime = ## Shorthand for `t.inZone(local())`. t.inZone(local()) -proc now*(): DateTime {.tags: [TimeEffect], benign.} = +proc now*(): DateTime {.tags: [TimeEffect], gcsafe.} = ## Get the current time as a `DateTime` in the local timezone. ## Shorthand for `getTime().local`. ## @@ -2327,7 +2327,7 @@ proc parseTime*(input: string, f: static[string], zone: Timezone): Time const f2 = initTimeFormat(f) result = input.parse(f2, zone).toTime() -proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} = +proc `$`*(dt: DateTime): string {.tags: [], raises: [], gcsafe.} = ## Converts a `DateTime` object to a string representation. ## It uses the format `yyyy-MM-dd'T'HH:mm:sszzz`. runnableExamples: @@ -2339,7 +2339,7 @@ proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} = else: result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz") -proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = +proc `$`*(time: Time): string {.tags: [], raises: [], gcsafe.} = ## Converts a `Time` value to a string representation. It will use the local ## time zone and use the format `yyyy-MM-dd'T'HH:mm:sszzz`. runnableExamples: diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index 508181316e..3043754f03 100644 --- a/lib/pure/typetraits.nim +++ b/lib/pure/typetraits.nim @@ -96,6 +96,9 @@ proc supportsCopyMem*(t: typedesc): bool {.magic: "TypeTrait".} ## ## Other languages name a type like these `blob`:idx:. +proc canFormCycles*(t: typedesc): bool {.magic: "TypeTrait".} + ## Returns true if `t` can form cycles. + proc hasDefaultValue*(t: typedesc): bool {.magic: "TypeTrait".} = ## Returns true if `t` has a valid default value. runnableExamples: diff --git a/lib/std/private/osdirs.nim b/lib/std/private/osdirs.nim index 5c6aa3e4d9..5c8ca2f432 100644 --- a/lib/std/private/osdirs.nim +++ b/lib/std/private/osdirs.nim @@ -331,7 +331,7 @@ proc rawRemoveDir(dir: string) {.noWeirdTarget.} = if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError(), dir) proc removeDir*(dir: string, checkDir = false) {.rtl, extern: "nos$1", tags: [ - WriteDirEffect, ReadDirEffect], benign, noWeirdTarget.} = + WriteDirEffect, ReadDirEffect], gcsafe, noWeirdTarget.} = ## Removes the directory `dir` including all subdirectories and files ## in `dir` (recursively). ## @@ -441,7 +441,7 @@ proc createDir*(dir: string) {.rtl, extern: "nos$1", discard existsOrCreateDir(p) proc copyDir*(source, dest: string, skipSpecial = false) {.rtl, extern: "nos$1", - tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect], benign, noWeirdTarget.} = + tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect], gcsafe, noWeirdTarget.} = ## Copies a directory from `source` to `dest`. ## ## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks @@ -482,7 +482,7 @@ proc copyDirWithPermissions*(source, dest: string, ignorePermissionErrors = true, skipSpecial = false) {.rtl, extern: "nos$1", tags: [ReadDirEffect, WriteIOEffect, ReadIOEffect], - benign, noWeirdTarget.} = + gcsafe, noWeirdTarget.} = ## Copies a directory from `source` to `dest` preserving file permissions. ## ## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks diff --git a/lib/std/syncio.nim b/lib/std/syncio.nim index 2aafb40e93..164b35666a 100644 --- a/lib/std/syncio.nim +++ b/lib/std/syncio.nim @@ -182,7 +182,7 @@ proc checkErr(f: File) = {.push stackTrace: off, profiler: off.} proc readBuffer*(f: File, buffer: pointer, len: Natural): int {. - tags: [ReadIOEffect], benign.} = + tags: [ReadIOEffect], gcsafe.} = ## Reads `len` bytes into the buffer pointed to by `buffer`. Returns ## the actual number of bytes that have been read which may be less than ## `len` (if not as many bytes are remaining), but not greater. @@ -191,20 +191,20 @@ proc readBuffer*(f: File, buffer: pointer, len: Natural): int {. proc readBytes*(f: File, a: var openArray[int8|uint8], start, len: Natural): int {. - tags: [ReadIOEffect], benign.} = + tags: [ReadIOEffect], gcsafe.} = ## Reads `len` bytes into the buffer `a` starting at `a[start]`. Returns ## the actual number of bytes that have been read which may be less than ## `len` (if not as many bytes are remaining), but not greater. result = readBuffer(f, addr(a[start]), len) -proc readChars*(f: File, a: var openArray[char]): int {.tags: [ReadIOEffect], benign.} = +proc readChars*(f: File, a: var openArray[char]): int {.tags: [ReadIOEffect], gcsafe.} = ## Reads up to `a.len` bytes into the buffer `a`. Returns ## the actual number of bytes that have been read which may be less than ## `a.len` (if not as many bytes are remaining), but not greater. result = readBuffer(f, addr(a[0]), a.len) proc readChars*(f: File, a: var openArray[char], start, len: Natural): int {. - tags: [ReadIOEffect], benign, deprecated: + tags: [ReadIOEffect], gcsafe, deprecated: "use other `readChars` overload, possibly via: readChars(toOpenArray(buf, start, len-1))".} = ## Reads `len` bytes into the buffer `a` starting at `a[start]`. Returns ## the actual number of bytes that have been read which may be less than @@ -213,13 +213,13 @@ proc readChars*(f: File, a: var openArray[char], start, len: Natural): int {. raiseEIO("buffer overflow: (start+len) > length of openarray buffer") result = readBuffer(f, addr(a[start]), len) -proc write*(f: File, c: cstring) {.tags: [WriteIOEffect], benign.} = +proc write*(f: File, c: cstring) {.tags: [WriteIOEffect], gcsafe.} = ## Writes a value to the file `f`. May throw an IO exception. discard c_fputs(c, f) checkErr(f) proc writeBuffer*(f: File, buffer: pointer, len: Natural): int {. - tags: [WriteIOEffect], benign.} = + tags: [WriteIOEffect], gcsafe.} = ## Writes the bytes of buffer pointed to by the parameter `buffer` to the ## file `f`. Returns the number of actual written bytes, which may be less ## than `len` in case of an error. @@ -227,7 +227,7 @@ proc writeBuffer*(f: File, buffer: pointer, len: Natural): int {. checkErr(f) proc writeBytes*(f: File, a: openArray[int8|uint8], start, len: Natural): int {. - tags: [WriteIOEffect], benign.} = + tags: [WriteIOEffect], gcsafe.} = ## Writes the bytes of `a[start..start+len-1]` to the file `f`. Returns ## the number of actual written bytes, which may be less than `len` in case ## of an error. @@ -235,7 +235,7 @@ proc writeBytes*(f: File, a: openArray[int8|uint8], start, len: Natural): int {. result = writeBuffer(f, addr(x[int(start)]), len) proc writeChars*(f: File, a: openArray[char], start, len: Natural): int {. - tags: [WriteIOEffect], benign.} = + tags: [WriteIOEffect], gcsafe.} = ## Writes the bytes of `a[start..start+len-1]` to the file `f`. Returns ## the number of actual written bytes, which may be less than `len` in case ## of an error. @@ -264,7 +264,7 @@ when defined(windows): break inc i, w -proc write*(f: File, s: string) {.tags: [WriteIOEffect], benign.} = +proc write*(f: File, s: string) {.tags: [WriteIOEffect], gcsafe.} = when defined(windows): writeWindows(f, s, doRaise = true) else: @@ -393,7 +393,7 @@ when defined(nimdoc) or (defined(posix) and not defined(nimscript)) or defined(w inheritable.WinDWORD) != 0 proc readLine*(f: File, line: var string): bool {.tags: [ReadIOEffect], - benign.} = + gcsafe.} = ## Reads a line of text from the file `f` into `line`. May throw an IO ## exception. ## A line of text may be delimited by `LF` or `CRLF`. The newline @@ -519,43 +519,43 @@ proc readLine*(f: File, line: var string): bool {.tags: [ReadIOEffect], sp = 128 # read in 128 bytes at a time line.setLen(pos+sp) -proc readLine*(f: File): string {.tags: [ReadIOEffect], benign.} = +proc readLine*(f: File): string {.tags: [ReadIOEffect], gcsafe.} = ## Reads a line of text from the file `f`. May throw an IO exception. ## A line of text may be delimited by `LF` or `CRLF`. The newline ## character(s) are not part of the returned string. result = newStringOfCap(80) if not readLine(f, result): raiseEOF() -proc write*(f: File, i: int) {.tags: [WriteIOEffect], benign.} = +proc write*(f: File, i: int) {.tags: [WriteIOEffect], gcsafe.} = when sizeof(int) == 8: if c_fprintf(f, "%lld", i) < 0: checkErr(f) else: if c_fprintf(f, "%ld", i) < 0: checkErr(f) -proc write*(f: File, i: BiggestInt) {.tags: [WriteIOEffect], benign.} = +proc write*(f: File, i: BiggestInt) {.tags: [WriteIOEffect], gcsafe.} = when sizeof(BiggestInt) == 8: if c_fprintf(f, "%lld", i) < 0: checkErr(f) else: if c_fprintf(f, "%ld", i) < 0: checkErr(f) -proc write*(f: File, b: bool) {.tags: [WriteIOEffect], benign.} = +proc write*(f: File, b: bool) {.tags: [WriteIOEffect], gcsafe.} = if b: write(f, "true") else: write(f, "false") -proc write*(f: File, r: float32) {.tags: [WriteIOEffect], benign.} = +proc write*(f: File, r: float32) {.tags: [WriteIOEffect], gcsafe.} = var buffer {.noinit.}: array[65, char] discard writeFloatToBuffer(buffer, r) if c_fprintf(f, "%s", buffer[0].addr) < 0: checkErr(f) -proc write*(f: File, r: BiggestFloat) {.tags: [WriteIOEffect], benign.} = +proc write*(f: File, r: BiggestFloat) {.tags: [WriteIOEffect], gcsafe.} = var buffer {.noinit.}: array[65, char] discard writeFloatToBuffer(buffer, r) if c_fprintf(f, "%s", buffer[0].addr) < 0: checkErr(f) -proc write*(f: File, c: char) {.tags: [WriteIOEffect], benign.} = +proc write*(f: File, c: char) {.tags: [WriteIOEffect], gcsafe.} = discard c_putc(cint(c), f) -proc write*(f: File, a: varargs[string, `$`]) {.tags: [WriteIOEffect], benign.} = +proc write*(f: File, a: varargs[string, `$`]) {.tags: [WriteIOEffect], gcsafe.} = for x in items(a): write(f, x) proc readAllBuffer(file: File): string = @@ -579,7 +579,7 @@ proc rawFileSize(file: File): int64 = result = c_ftell(file) discard c_fseek(file, oldPos, 0) -proc endOfFile*(f: File): bool {.tags: [], benign.} = +proc endOfFile*(f: File): bool {.tags: [], gcsafe.} = ## Returns true if `f` is at the end. var c = c_fgetc(f) discard c_ungetc(c, f) @@ -603,7 +603,7 @@ proc readAllFile(file: File): string = var len = rawFileSize(file) result = readAllFile(file, len) -proc readAll*(file: File): string {.tags: [ReadIOEffect], benign.} = +proc readAll*(file: File): string {.tags: [ReadIOEffect], gcsafe.} = ## Reads all data from the stream `file`. ## ## Raises an IO exception in case of an error. It is an error if the @@ -621,7 +621,7 @@ proc readAll*(file: File): string {.tags: [ReadIOEffect], benign.} = result = readAllBuffer(file) proc writeLine*[Ty](f: File, x: varargs[Ty, `$`]) {.inline, - tags: [WriteIOEffect], benign.} = + tags: [WriteIOEffect], gcsafe.} = ## Writes the values `x` to `f` and then writes "\\n". ## May throw an IO exception. for i in items(x): @@ -713,7 +713,7 @@ when defined(posix) and not defined(nimscript): proc open*(f: var File, filename: string, mode: FileMode = fmRead, - bufSize: int = -1): bool {.tags: [], raises: [], benign.} = + bufSize: int = -1): bool {.tags: [], raises: [], gcsafe.} = ## Opens a file named `filename` with given `mode`. ## ## Default mode is readonly. Returns true if the file could be opened. @@ -747,7 +747,7 @@ proc open*(f: var File, filename: string, result = false proc reopen*(f: File, filename: string, mode: FileMode = fmRead): bool {. - tags: [], benign.} = + tags: [], gcsafe.} = ## Reopens the file `f` with given `filename` and `mode`. This ## is often used to redirect the `stdin`, `stdout` or `stderr` ## file variables. @@ -766,7 +766,7 @@ proc reopen*(f: File, filename: string, mode: FileMode = fmRead): bool {. result = false proc open*(f: var File, filehandle: FileHandle, - mode: FileMode = fmRead): bool {.tags: [], raises: [], benign.} = + mode: FileMode = fmRead): bool {.tags: [], raises: [], gcsafe.} = ## Creates a `File` from a `filehandle` with given `mode`. ## ## Default mode is readonly. Returns true if the file could be opened. @@ -792,26 +792,26 @@ proc open*(filename: string, if not open(result, filename, mode, bufSize): raise newException(IOError, "cannot open: " & filename) -proc setFilePos*(f: File, pos: int64, relativeTo: FileSeekPos = fspSet) {.benign, sideEffect.} = +proc setFilePos*(f: File, pos: int64, relativeTo: FileSeekPos = fspSet) {.gcsafe, sideEffect.} = ## Sets the position of the file pointer that is used for read/write ## operations. The file's first byte has the index zero. if c_fseek(f, pos, cint(relativeTo)) != 0: raiseEIO("cannot set file position") -proc getFilePos*(f: File): int64 {.benign.} = +proc getFilePos*(f: File): int64 {.gcsafe.} = ## Retrieves the current position of the file pointer that is used to ## read from the file `f`. The file's first byte has the index zero. result = c_ftell(f) if result < 0: raiseEIO("cannot retrieve file position") -proc getFileSize*(f: File): int64 {.tags: [ReadIOEffect], benign.} = +proc getFileSize*(f: File): int64 {.tags: [ReadIOEffect], gcsafe.} = ## Retrieves the file size (in bytes) of `f`. let oldPos = getFilePos(f) discard c_fseek(f, 0, 2) # seek the end of the file result = getFilePos(f) setFilePos(f, oldPos) -proc setStdIoUnbuffered*() {.tags: [], benign.} = +proc setStdIoUnbuffered*() {.tags: [], gcsafe.} = ## Configures `stdin`, `stdout` and `stderr` to be unbuffered. when declared(stdout): discard c_setvbuf(stdout, nil, IONBF, 0) @@ -865,7 +865,7 @@ when defined(windows) and appType == "console" and discard setConsoleCP(Utf8codepage) addExitProc(restoreConsoleCP) -proc readFile*(filename: string): string {.tags: [ReadIOEffect], benign.} = +proc readFile*(filename: string): string {.tags: [ReadIOEffect], gcsafe.} = ## Opens a file named `filename` for reading, calls `readAll ## <#readAll,File>`_ and closes the file afterwards. Returns the string. ## Raises an IO exception in case of an error. If you need to call @@ -880,7 +880,7 @@ proc readFile*(filename: string): string {.tags: [ReadIOEffect], benign.} = else: raise newException(IOError, "cannot open: " & filename) -proc writeFile*(filename, content: string) {.tags: [WriteIOEffect], benign.} = +proc writeFile*(filename, content: string) {.tags: [WriteIOEffect], gcsafe.} = ## Opens a file named `filename` for writing. Then writes the ## `content` completely to the file and closes the file afterwards. ## Raises an IO exception in case of an error. diff --git a/lib/system.nim b/lib/system.nim index 6104c1b928..306818ffa0 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -627,7 +627,7 @@ proc newSeq*[T](s: var seq[T], len: Natural) {.magic: "NewSeq", noSideEffect.} ## #inputStrings[3] = "out of bounds" ## ``` -proc newSeq*[T](len = 0.Natural): seq[T] = +proc newSeq*[T](len = 0.Natural): seq[T] {.noSideEffect.} = ## Creates a new sequence of type `seq[T]` with length `len`. ## ## Note that the sequence will be filled with zeroed entries. @@ -1147,7 +1147,7 @@ template sysAssert(cond: bool, msg: string) = const hasAlloc = (hostOS != "standalone" or not defined(nogc)) and not defined(nimscript) when notJSnotNims and hasAlloc and not defined(nimSeqsV2): - proc addChar(s: NimString, c: char): NimString {.compilerproc, benign.} + proc addChar(s: NimString, c: char): NimString {.compilerproc, gcsafe.} when defined(nimscript) or not defined(nimSeqsV2): proc add*[T](x: var seq[T], y: sink T) {.magic: "AppendSeqElem", noSideEffect.} @@ -1459,6 +1459,7 @@ proc isNil*[T: proc | iterator {.closure.}](x: T): bool {.noSideEffect, magic: " ## `== nil`. proc supportsCopyMem(t: typedesc): bool {.magic: "TypeTrait".} +proc canFormCycles(t: typedesc): bool {.magic: "TypeTrait".} when defined(nimHasTopDownInference): # magic used for seq type inference @@ -1664,7 +1665,7 @@ when not defined(js) and hasThreadSupport and hostOS != "standalone": when not defined(js) and defined(nimV2): type - DestructorProc = proc (p: pointer) {.nimcall, benign, raises: [].} + DestructorProc = proc (p: pointer) {.nimcall, gcsafe, raises: [].} TNimTypeV2 {.compilerproc.} = object destructor: pointer size: int @@ -1776,7 +1777,7 @@ when not defined(nimscript): when not declared(sysFatal): include "system/fatal" -proc echo*(x: varargs[typed, `$`]) {.magic: "Echo", benign, sideEffect.} +proc echo*(x: varargs[typed, `$`]) {.magic: "Echo", gcsafe, sideEffect.} ## Writes and flushes the parameters to the standard output. ## ## Special built-in that takes a variable number of arguments. Each argument @@ -1883,7 +1884,7 @@ when notJSnotNims: ## lead to the `raise` statement. This only works for debug builds. var - globalRaiseHook*: proc (e: ref Exception): bool {.nimcall, benign.} + globalRaiseHook*: proc (e: ref Exception): bool {.nimcall, gcsafe.} ## With this hook you can influence exception handling on a global level. ## If not nil, every 'raise' statement ends up calling this hook. ## @@ -1892,7 +1893,7 @@ when notJSnotNims: ## If `globalRaiseHook` returns false, the exception is caught and does ## not propagate further through the call stack. - localRaiseHook* {.threadvar.}: proc (e: ref Exception): bool {.nimcall, benign.} + localRaiseHook* {.threadvar.}: proc (e: ref Exception): bool {.nimcall, gcsafe.} ## With this hook you can influence exception handling on a ## thread local level. ## If not nil, every 'raise' statement ends up calling this hook. @@ -1902,7 +1903,7 @@ when notJSnotNims: ## If `localRaiseHook` returns false, the exception ## is caught and does not propagate further through the call stack. - outOfMemHook*: proc () {.nimcall, tags: [], benign, raises: [].} + outOfMemHook*: proc () {.nimcall, tags: [], gcsafe, raises: [].} ## Set this variable to provide a procedure that should be called ## in case of an `out of memory`:idx: event. The standard handler ## writes an error message and terminates the program. @@ -1923,7 +1924,7 @@ when notJSnotNims: ## If the handler does not raise an exception, ordinary control flow ## continues and the program is terminated. - unhandledExceptionHook*: proc (e: ref Exception) {.nimcall, tags: [], benign, raises: [].} + unhandledExceptionHook*: proc (e: ref Exception) {.nimcall, tags: [], gcsafe, raises: [].} ## Set this variable to provide a procedure that should be called ## in case of an `unhandle exception` event. The standard handler ## writes an error message and terminates the program, except when @@ -2066,7 +2067,7 @@ when hostOS == "standalone" and defined(nogc): if s == nil or s.len == 0: result = cstring"" else: result = cast[cstring](addr s.data) -proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo", benign.} +proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo", gcsafe.} ## Get type information for `x`. ## ## Ordinary code should not use this, but the `typeinfo module @@ -2285,21 +2286,21 @@ when not defined(js) and declared(alloc0) and declared(dealloc): dealloc(a) when notJSnotNims and hostOS != "standalone": - proc getCurrentException*(): ref Exception {.compilerRtl, inl, benign.} = + proc getCurrentException*(): ref Exception {.compilerRtl, inl, gcsafe.} = ## Retrieves the current exception; if there is none, `nil` is returned. result = currException - proc nimBorrowCurrentException(): ref Exception {.compilerRtl, inl, benign, nodestroy.} = + proc nimBorrowCurrentException(): ref Exception {.compilerRtl, inl, gcsafe, nodestroy.} = # .nodestroy here so that we do not produce a write barrier as the # C codegen only uses it in a borrowed way: result = currException - proc getCurrentExceptionMsg*(): string {.inline, benign.} = + proc getCurrentExceptionMsg*(): string {.inline, gcsafe.} = ## Retrieves the error message that was attached to the current ## exception; if there is none, `""` is returned. return if currException == nil: "" else: currException.msg - proc setCurrentException*(exc: ref Exception) {.inline, benign.} = + proc setCurrentException*(exc: ref Exception) {.inline, gcsafe.} = ## Sets the current exception. ## ## .. warning:: Only use this if you know what you are doing. diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index a23702dc1f..1c2706120e 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -725,7 +725,7 @@ proc getSmallChunk(a: var MemRegion): PSmallChunk = # ----------------------------------------------------------------------------- when not defined(gcDestructors): - proc isAllocatedPtr(a: MemRegion, p: pointer): bool {.benign.} + proc isAllocatedPtr(a: MemRegion, p: pointer): bool {.gcsafe.} when true: template allocInv(a: MemRegion): bool = true diff --git a/lib/system/assign.nim b/lib/system/assign.nim index 9f4cbc0feb..0955222ec1 100644 --- a/lib/system/assign.nim +++ b/lib/system/assign.nim @@ -9,11 +9,11 @@ include seqs_v2_reimpl -proc genericResetAux(dest: pointer, n: ptr TNimNode) {.benign.} +proc genericResetAux(dest: pointer, n: ptr TNimNode) {.gcsafe.} -proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) {.benign.} +proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) {.gcsafe.} proc genericAssignAux(dest, src: pointer, n: ptr TNimNode, - shallow: bool) {.benign.} = + shallow: bool) {.gcsafe.} = var d = cast[int](dest) s = cast[int](src) @@ -187,8 +187,8 @@ proc genericAssignOpenArray(dest, src: pointer, len: int, genericAssign(cast[pointer](d +% i *% mt.base.size), cast[pointer](s +% i *% mt.base.size), mt.base) -proc objectInit(dest: pointer, typ: PNimType) {.compilerproc, benign.} -proc objectInitAux(dest: pointer, n: ptr TNimNode) {.benign.} = +proc objectInit(dest: pointer, typ: PNimType) {.compilerproc, gcsafe.} +proc objectInitAux(dest: pointer, n: ptr TNimNode) {.gcsafe.} = var d = cast[int](dest) case n.kind of nkNone: sysAssert(false, "objectInitAux") @@ -224,7 +224,7 @@ proc objectInit(dest: pointer, typ: PNimType) = # ---------------------- assign zero ----------------------------------------- -proc genericReset(dest: pointer, mt: PNimType) {.compilerproc, benign.} +proc genericReset(dest: pointer, mt: PNimType) {.compilerproc, gcsafe.} proc genericResetAux(dest: pointer, n: ptr TNimNode) = var d = cast[int](dest) case n.kind diff --git a/lib/system/avltree.nim b/lib/system/avltree.nim index 8d4b7e8974..b9020565a5 100644 --- a/lib/system/avltree.nim +++ b/lib/system/avltree.nim @@ -51,7 +51,7 @@ proc split(t: var PAvlNode) = t.link[0] = temp inc t.level -proc add(a: var MemRegion, t: var PAvlNode, key, upperBound: int) {.benign.} = +proc add(a: var MemRegion, t: var PAvlNode, key, upperBound: int) {.gcsafe.} = if t.isBottom: t = allocAvlNode(a, key, upperBound) else: @@ -70,7 +70,7 @@ proc add(a: var MemRegion, t: var PAvlNode, key, upperBound: int) {.benign.} = skew(t) split(t) -proc del(a: var MemRegion, t: var PAvlNode, x: int) {.benign.} = +proc del(a: var MemRegion, t: var PAvlNode, x: int) {.gcsafe.} = if isBottom(t): return a.last = t if x <% t.key: diff --git a/lib/system/channels_builtin.nim b/lib/system/channels_builtin.nim index 2123707301..9534c9b45e 100644 --- a/lib/system/channels_builtin.nim +++ b/lib/system/channels_builtin.nim @@ -181,10 +181,10 @@ proc deinitRawChannel(p: pointer) = when not usesDestructors: proc storeAux(dest, src: pointer, mt: PNimType, t: PRawChannel, - mode: LoadStoreMode) {.benign.} + mode: LoadStoreMode) {.gcsafe.} proc storeAux(dest, src: pointer, n: ptr TNimNode, t: PRawChannel, - mode: LoadStoreMode) {.benign.} = + mode: LoadStoreMode) {.gcsafe.} = var d = cast[int](dest) s = cast[int](src) diff --git a/lib/system/cyclebreaker.nim b/lib/system/cyclebreaker.nim index d611322d96..9ee8a98305 100644 --- a/lib/system/cyclebreaker.nim +++ b/lib/system/cyclebreaker.nim @@ -62,8 +62,8 @@ const colorMask = 0b011 type - TraceProc = proc (p, env: pointer) {.nimcall, benign, raises: [].} - DisposeProc = proc (p: pointer) {.nimcall, benign, raises: [].} + TraceProc = proc (p, env: pointer) {.nimcall, gcsafe, raises: [].} + DisposeProc = proc (p: pointer) {.nimcall, gcsafe, raises: [].} template color(c): untyped = c.rc and colorMask template setColor(c, col) = diff --git a/lib/system/deepcopy.nim b/lib/system/deepcopy.nim index 0f7d0eaae2..fdf1499e5f 100644 --- a/lib/system/deepcopy.nim +++ b/lib/system/deepcopy.nim @@ -58,9 +58,9 @@ proc put(t: var PtrTable; key, val: pointer) = inc t.counter proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; - tab: var PtrTable) {.benign.} + tab: var PtrTable) {.gcsafe.} proc genericDeepCopyAux(dest, src: pointer, n: ptr TNimNode; - tab: var PtrTable) {.benign.} = + tab: var PtrTable) {.gcsafe.} = var d = cast[int](dest) s = cast[int](src) diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index 12552515cc..0218190607 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -16,7 +16,7 @@ import stacktraces const noStacktraceAvailable = "No stack traceback available\n" var - errorMessageWriter*: (proc(msg: string) {.tags: [WriteIOEffect], benign, + errorMessageWriter*: (proc(msg: string) {.tags: [WriteIOEffect], gcsafe, nimcall, raises: [].}) ## Function that will be called ## instead of `stdmsg.write` when printing stacktrace. @@ -61,10 +61,10 @@ proc showErrorMessage2(data: string) {.inline.} = # TODO showErrorMessage will turn it back to a string when a hook is set (!) showErrorMessage(data.cstring, data.len) -proc chckIndx(i, a, b: int): int {.inline, compilerproc, benign.} -proc chckRange(i, a, b: int): int {.inline, compilerproc, benign.} -proc chckRangeF(x, a, b: float): float {.inline, compilerproc, benign.} -proc chckNil(p: pointer) {.noinline, compilerproc, benign.} +proc chckIndx(i, a, b: int): int {.inline, compilerproc, gcsafe.} +proc chckRange(i, a, b: int): int {.inline, compilerproc, gcsafe.} +proc chckRangeF(x, a, b: float): float {.inline, compilerproc, gcsafe.} +proc chckNil(p: pointer) {.noinline, compilerproc, gcsafe.} type GcFrame = ptr GcFrameHeader @@ -653,7 +653,7 @@ when defined(cpp) and appType != "lib" and not gotoBasedExceptions and rawQuit 1 when not defined(noSignalHandler) and not defined(useNimRtl): - type Sighandler = proc (a: cint) {.noconv, benign.} + type Sighandler = proc (a: cint) {.noconv, gcsafe.} # xxx factor with ansi_c.CSighandlerT, posix.Sighandler proc signalHandler(sign: cint) {.exportc: "signalHandler", noconv, raises: [].} = diff --git a/lib/system/gc.nim b/lib/system/gc.nim index bc199b8351..861e0704f5 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -76,7 +76,7 @@ const when withRealTime and not declared(getTicks): include "system/timers" when defined(memProfiler): - proc nimProfile(requestedSize: int) {.benign.} + proc nimProfile(requestedSize: int) {.gcsafe.} when hasThreadSupport: import std/sharedlist @@ -97,7 +97,7 @@ type waZctDecRef, waPush #, waDebug - Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign, raises: [], gcsafe.} + Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, gcsafe, raises: [].} # A ref type can have a finalizer that is called before the object's # storage is freed. @@ -222,11 +222,11 @@ template gcTrace(cell, state: untyped) = when traceGC: traceCell(cell, state) # forward declarations: -proc collectCT(gch: var GcHeap) {.benign, raises: [].} -proc isOnStack(p: pointer): bool {.noinline, benign, raises: [].} -proc forAllChildren(cell: PCell, op: WalkOp) {.benign, raises: [].} -proc doOperation(p: pointer, op: WalkOp) {.benign, raises: [].} -proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) {.benign, raises: [].} +proc collectCT(gch: var GcHeap) {.gcsafe, raises: [].} +proc isOnStack(p: pointer): bool {.noinline, gcsafe, raises: [].} +proc forAllChildren(cell: PCell, op: WalkOp) {.gcsafe, raises: [].} +proc doOperation(p: pointer, op: WalkOp) {.gcsafe, raises: [].} +proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) {.gcsafe, raises: [].} # we need the prototype here for debugging purposes proc incRef(c: PCell) {.inline.} = @@ -338,7 +338,7 @@ proc cellsetReset(s: var CellSet) = {.push stacktrace:off.} -proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.benign.} = +proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.gcsafe.} = var d = cast[int](dest) case n.kind of nkSlot: forAllChildrenAux(cast[pointer](d +% n.offset), n.typ, op) @@ -687,7 +687,7 @@ proc doOperation(p: pointer, op: WalkOp) = proc nimGCvisit(d: pointer, op: int) {.compilerRtl, raises: [].} = doOperation(d, WalkOp(op)) -proc collectZCT(gch: var GcHeap): bool {.benign, raises: [].} +proc collectZCT(gch: var GcHeap): bool {.gcsafe, raises: [].} proc collectCycles(gch: var GcHeap) {.raises: [].} = when hasThreadSupport: diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim index eb08845603..08e8798b08 100644 --- a/lib/system/gc_common.nim +++ b/lib/system/gc_common.nim @@ -457,7 +457,7 @@ proc deallocHeap*(runFinalizers = true; allowGcAfterwards = true) = initGC() type - GlobalMarkerProc = proc () {.nimcall, benign, raises: [].} + GlobalMarkerProc = proc () {.nimcall, gcsafe, raises: [].} var globalMarkersLen {.exportc.}: int globalMarkers {.exportc.}: array[0..3499, GlobalMarkerProc] diff --git a/lib/system/gc_hooks.nim b/lib/system/gc_hooks.nim index 936b31b20a..a8ecdd2f51 100644 --- a/lib/system/gc_hooks.nim +++ b/lib/system/gc_hooks.nim @@ -11,7 +11,7 @@ ## collectors etc. type - GlobalMarkerProc = proc () {.nimcall, benign, raises: [], tags: [].} + GlobalMarkerProc = proc () {.nimcall, gcsafe, raises: [], tags: [].} var globalMarkersLen: int globalMarkers: array[0..3499, GlobalMarkerProc] diff --git a/lib/system/gc_interface.nim b/lib/system/gc_interface.nim index b34ce4a566..256efbe547 100644 --- a/lib/system/gc_interface.nim +++ b/lib/system/gc_interface.nim @@ -12,7 +12,7 @@ when hasAlloc: gcOptimizeSpace ## optimize for memory footprint when hasAlloc and not defined(js) and not usesDestructors: - proc GC_disable*() {.rtl, inl, benign, raises: [].} + proc GC_disable*() {.rtl, inl, gcsafe, raises: [].} ## Disables the GC. If called `n` times, `n` calls to `GC_enable` ## are needed to reactivate the GC. ## @@ -20,39 +20,39 @@ when hasAlloc and not defined(js) and not usesDestructors: ## the mark and sweep phase with ## `GC_disableMarkAndSweep <#GC_disableMarkAndSweep>`_. - proc GC_enable*() {.rtl, inl, benign, raises: [].} + proc GC_enable*() {.rtl, inl, gcsafe, raises: [].} ## Enables the GC again. - proc GC_fullCollect*() {.rtl, benign, raises: [].} + proc GC_fullCollect*() {.rtl, gcsafe, raises: [].} ## Forces a full garbage collection pass. ## Ordinary code does not need to call this (and should not). - proc GC_enableMarkAndSweep*() {.rtl, benign, raises: [].} - proc GC_disableMarkAndSweep*() {.rtl, benign, raises: [].} + proc GC_enableMarkAndSweep*() {.rtl, gcsafe, raises: [].} + proc GC_disableMarkAndSweep*() {.rtl, gcsafe, raises: [].} ## The current implementation uses a reference counting garbage collector ## with a seldomly run mark and sweep phase to free cycles. The mark and ## sweep phase may take a long time and is not needed if the application ## does not create cycles. Thus the mark and sweep phase can be deactivated ## and activated separately from the rest of the GC. - proc GC_getStatistics*(): string {.rtl, benign, raises: [].} + proc GC_getStatistics*(): string {.rtl, gcsafe, raises: [].} ## Returns an informative string about the GC's activity. This may be useful ## for tweaking. - proc GC_ref*[T](x: ref T) {.magic: "GCref", benign, raises: [].} - proc GC_ref*[T](x: seq[T]) {.magic: "GCref", benign, raises: [].} - proc GC_ref*(x: string) {.magic: "GCref", benign, raises: [].} + proc GC_ref*[T](x: ref T) {.magic: "GCref", gcsafe, raises: [].} + proc GC_ref*[T](x: seq[T]) {.magic: "GCref", gcsafe, raises: [].} + proc GC_ref*(x: string) {.magic: "GCref", gcsafe, raises: [].} ## Marks the object `x` as referenced, so that it will not be freed until ## it is unmarked via `GC_unref`. ## If called n-times for the same object `x`, ## n calls to `GC_unref` are needed to unmark `x`. - proc GC_unref*[T](x: ref T) {.magic: "GCunref", benign, raises: [].} - proc GC_unref*[T](x: seq[T]) {.magic: "GCunref", benign, raises: [].} - proc GC_unref*(x: string) {.magic: "GCunref", benign, raises: [].} + proc GC_unref*[T](x: ref T) {.magic: "GCunref", gcsafe, raises: [].} + proc GC_unref*[T](x: seq[T]) {.magic: "GCunref", gcsafe, raises: [].} + proc GC_unref*(x: string) {.magic: "GCunref", gcsafe, raises: [].} ## See the documentation of `GC_ref <#GC_ref,string>`_. - proc nimGC_setStackBottom*(theStackBottom: pointer) {.compilerRtl, noinline, benign, raises: [].} + proc nimGC_setStackBottom*(theStackBottom: pointer) {.compilerRtl, noinline, gcsafe, raises: [].} ## Expands operating GC stack range to `theStackBottom`. Does nothing ## if current stack bottom is already lower than `theStackBottom`. diff --git a/lib/system/gc_ms.nim b/lib/system/gc_ms.nim index 9efca9cbae..fcaae690ba 100644 --- a/lib/system/gc_ms.nim +++ b/lib/system/gc_ms.nim @@ -36,7 +36,7 @@ type # local waMarkPrecise # fast precise marking - Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign, raises: [], gcsafe.} + Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, gcsafe, raises: [].} # A ref type can have a finalizer that is called before the object's # storage is freed. @@ -115,10 +115,10 @@ when BitsPerPage mod (sizeof(int)*8) != 0: {.error: "(BitsPerPage mod BitsPerUnit) should be zero!".} # forward declarations: -proc collectCT(gch: var GcHeap; size: int) {.benign, raises: [].} -proc forAllChildren(cell: PCell, op: WalkOp) {.benign, raises: [].} -proc doOperation(p: pointer, op: WalkOp) {.benign, raises: [].} -proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) {.benign, raises: [].} +proc collectCT(gch: var GcHeap; size: int) {.gcsafe, raises: [].} +proc forAllChildren(cell: PCell, op: WalkOp) {.gcsafe, raises: [].} +proc doOperation(p: pointer, op: WalkOp) {.gcsafe, raises: [].} +proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) {.gcsafe, raises: [].} # we need the prototype here for debugging purposes when defined(nimGcRefLeak): @@ -216,7 +216,7 @@ proc initGC() = gch.gcThreadId = atomicInc(gHeapidGenerator) - 1 gcAssert(gch.gcThreadId >= 0, "invalid computed thread ID") -proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.benign.} = +proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.gcsafe.} = var d = cast[int](dest) case n.kind of nkSlot: forAllChildrenAux(cast[pointer](d +% n.offset), n.typ, op) diff --git a/lib/system/gc_regions.nim b/lib/system/gc_regions.nim index 0385e2963d..c1bf61d283 100644 --- a/lib/system/gc_regions.nim +++ b/lib/system/gc_regions.nim @@ -12,7 +12,7 @@ import std/private/syslocks when defined(memProfiler): - proc nimProfile(requestedSize: int) {.benign.} + proc nimProfile(requestedSize: int) {.gcsafe.} when defined(useMalloc): proc roundup(x, v: int): int {.inline.} = @@ -41,7 +41,7 @@ else: # We also support 'finalizers'. type - Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign, raises: [], gcsafe.} + Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, gcsafe, raises: [].} # A ref type can have a finalizer that is called before the object's # storage is freed. diff --git a/lib/system/hti.nim b/lib/system/hti.nim index a26aff9822..4ae61753af 100644 --- a/lib/system/hti.nim +++ b/lib/system/hti.nim @@ -96,8 +96,8 @@ type base*: ptr TNimType node: ptr TNimNode # valid for tyRecord, tyObject, tyTuple, tyEnum finalizer*: pointer # the finalizer for the type - marker*: proc (p: pointer, op: int) {.nimcall, benign, tags: [], raises: [].} # marker proc for GC - deepcopy: proc (p: pointer): pointer {.nimcall, benign, tags: [], raises: [].} + marker*: proc (p: pointer, op: int) {.nimcall, gcsafe, tags: [], raises: [].} # marker proc for GC + deepcopy: proc (p: pointer): pointer {.nimcall, gcsafe, tags: [], raises: [].} when defined(nimSeqsV2): typeInfoV2*: pointer when defined(nimTypeNames): diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index b469c4695f..96f35c3c0c 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -51,7 +51,7 @@ proc nimCharToStr(x: char): string {.compilerproc.} = proc isNimException(): bool {.asmNoStackFrame.} = {.emit: "return `lastJSError` && `lastJSError`.m_type;".} -proc getCurrentException*(): ref Exception {.compilerRtl, benign.} = +proc getCurrentException*(): ref Exception {.compilerRtl, gcsafe.} = if isNimException(): result = cast[ref Exception](lastJSError) proc getCurrentExceptionMsg*(): string = @@ -72,7 +72,7 @@ proc getCurrentExceptionMsg*(): string = proc setCurrentException*(exc: ref Exception) = lastJSError = cast[PJSError](exc) -proc closureIterSetExc(e: ref Exception) {.compilerRtl, benign.} = +proc closureIterSetExc(e: ref Exception) {.compilerRtl, gcsafe.} = setCurrentException(e) proc pushCurrentException(e: sink(ref Exception)) {.compilerRtl, inline.} = diff --git a/lib/system/memalloc.nim b/lib/system/memalloc.nim index b26f3af24d..ed0de06c19 100644 --- a/lib/system/memalloc.nim +++ b/lib/system/memalloc.nim @@ -6,7 +6,7 @@ when notJSnotNims: ## Exactly `size` bytes will be overwritten. Like any procedure ## dealing with raw memory this is **unsafe**. - proc copyMem*(dest, source: pointer, size: Natural) {.inline, benign, + proc copyMem*(dest, source: pointer, size: Natural) {.inline, gcsafe, tags: [], raises: [], enforceNoRaises.} ## Copies the contents from the memory at `source` to the memory ## at `dest`. @@ -14,7 +14,7 @@ when notJSnotNims: ## regions may not overlap. Like any procedure dealing with raw ## memory this is **unsafe**. - proc moveMem*(dest, source: pointer, size: Natural) {.inline, benign, + proc moveMem*(dest, source: pointer, size: Natural) {.inline, gcsafe, tags: [], raises: [], enforceNoRaises.} ## Copies the contents from the memory at `source` to the memory ## at `dest`. @@ -48,17 +48,17 @@ when notJSnotNims: when hasAlloc and not defined(js): - proc allocImpl*(size: Natural): pointer {.noconv, rtl, tags: [], benign, raises: [].} - proc alloc0Impl*(size: Natural): pointer {.noconv, rtl, tags: [], benign, raises: [].} - proc deallocImpl*(p: pointer) {.noconv, rtl, tags: [], benign, raises: [].} - proc reallocImpl*(p: pointer, newSize: Natural): pointer {.noconv, rtl, tags: [], benign, raises: [].} - proc realloc0Impl*(p: pointer, oldSize, newSize: Natural): pointer {.noconv, rtl, tags: [], benign, raises: [].} + proc allocImpl*(size: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].} + proc alloc0Impl*(size: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].} + proc deallocImpl*(p: pointer) {.noconv, rtl, tags: [], gcsafe, raises: [].} + proc reallocImpl*(p: pointer, newSize: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].} + proc realloc0Impl*(p: pointer, oldSize, newSize: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].} - proc allocSharedImpl*(size: Natural): pointer {.noconv, compilerproc, rtl, benign, raises: [], tags: [].} - proc allocShared0Impl*(size: Natural): pointer {.noconv, rtl, benign, raises: [], tags: [].} - proc deallocSharedImpl*(p: pointer) {.noconv, rtl, benign, raises: [], tags: [].} - proc reallocSharedImpl*(p: pointer, newSize: Natural): pointer {.noconv, rtl, tags: [], benign, raises: [].} - proc reallocShared0Impl*(p: pointer, oldSize, newSize: Natural): pointer {.noconv, rtl, tags: [], benign, raises: [].} + proc allocSharedImpl*(size: Natural): pointer {.noconv, compilerproc, rtl, gcsafe, raises: [], tags: [].} + proc allocShared0Impl*(size: Natural): pointer {.noconv, rtl, gcsafe, raises: [], tags: [].} + proc deallocSharedImpl*(p: pointer) {.noconv, rtl, gcsafe, raises: [], tags: [].} + proc reallocSharedImpl*(p: pointer, newSize: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].} + proc reallocShared0Impl*(p: pointer, oldSize, newSize: Natural): pointer {.noconv, rtl, tags: [], gcsafe, raises: [].} # Allocator statistics for memory leak tests @@ -103,7 +103,7 @@ when hasAlloc and not defined(js): incStat(allocCount) allocImpl(size) - proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline, benign, raises: [].} = + proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline, gcsafe, raises: [].} = ## Allocates a new memory block with at least `T.sizeof * size` bytes. ## ## The block has to be freed with `resize(block, 0) <#resize,ptr.T,Natural>`_ @@ -131,7 +131,7 @@ when hasAlloc and not defined(js): incStat(allocCount) alloc0Impl(size) - proc create*(T: typedesc, size = 1.Positive): ptr T {.inline, benign, raises: [].} = + proc create*(T: typedesc, size = 1.Positive): ptr T {.inline, gcsafe, raises: [].} = ## Allocates a new memory block with at least `T.sizeof * size` bytes. ## ## The block has to be freed with `resize(block, 0) <#resize,ptr.T,Natural>`_ @@ -174,7 +174,7 @@ when hasAlloc and not defined(js): ## from a shared heap. realloc0Impl(p, oldSize, newSize) - proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline, benign, raises: [].} = + proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline, gcsafe, raises: [].} = ## Grows or shrinks a given memory block. ## ## If `p` is **nil** then a new memory block is returned. @@ -187,7 +187,7 @@ when hasAlloc and not defined(js): ## from a shared heap. cast[ptr T](realloc(p, T.sizeof * newSize)) - proc dealloc*(p: pointer) {.noconv, compilerproc, rtl, benign, raises: [], tags: [].} = + proc dealloc*(p: pointer) {.noconv, compilerproc, rtl, gcsafe, raises: [], tags: [].} = ## Frees the memory allocated with `alloc`, `alloc0`, ## `realloc`, `create` or `createU`. ## @@ -218,7 +218,7 @@ when hasAlloc and not defined(js): allocSharedImpl(size) proc createSharedU*(T: typedesc, size = 1.Positive): ptr T {.inline, tags: [], - benign, raises: [].} = + gcsafe, raises: [].} = ## Allocates a new memory block on the shared heap with at ## least `T.sizeof * size` bytes. ## @@ -296,7 +296,7 @@ when hasAlloc and not defined(js): ## `freeShared <#freeShared,ptr.T>`_. cast[ptr T](reallocShared(p, T.sizeof * newSize)) - proc deallocShared*(p: pointer) {.noconv, compilerproc, rtl, benign, raises: [], tags: [].} = + proc deallocShared*(p: pointer) {.noconv, compilerproc, rtl, gcsafe, raises: [], tags: [].} = ## Frees the memory allocated with `allocShared`, `allocShared0` or ## `reallocShared`. ## @@ -307,7 +307,7 @@ when hasAlloc and not defined(js): incStat(deallocCount) deallocSharedImpl(p) - proc freeShared*[T](p: ptr T) {.inline, benign, raises: [].} = + proc freeShared*[T](p: ptr T) {.inline, gcsafe, raises: [].} = ## Frees the memory allocated with `createShared`, `createSharedU` or ## `resizeShared`. ## diff --git a/lib/system/orc.nim b/lib/system/orc.nim index 2b9ce22ec4..5be19fad93 100644 --- a/lib/system/orc.nim +++ b/lib/system/orc.nim @@ -29,8 +29,8 @@ const logOrc = defined(nimArcIds) type - TraceProc = proc (p, env: pointer) {.nimcall, benign, raises: [].} - DisposeProc = proc (p: pointer) {.nimcall, benign, raises: [].} + TraceProc = proc (p, env: pointer) {.nimcall, gcsafe, raises: [].} + DisposeProc = proc (p: pointer) {.nimcall, gcsafe, raises: [].} template color(c): untyped = c.rc and colorMask template setColor(c, col) = diff --git a/lib/system/repr.nim b/lib/system/repr.nim index 13118e40b2..e8ac1e41e5 100644 --- a/lib/system/repr.nim +++ b/lib/system/repr.nim @@ -77,7 +77,7 @@ include system/repr_impl type PByteArray = ptr UncheckedArray[byte] # array[0xffff, byte] -proc addSetElem(result: var string, elem: int, typ: PNimType) {.benign.} = +proc addSetElem(result: var string, elem: int, typ: PNimType) {.gcsafe.} = case typ.kind of tyEnum: add result, reprEnum(elem, typ) of tyBool: add result, reprBool(bool(elem)) @@ -147,7 +147,7 @@ when not defined(useNimRtl): for i in 0..cl.indent-1: add result, ' ' proc reprAux(result: var string, p: pointer, typ: PNimType, - cl: var ReprClosure) {.benign.} + cl: var ReprClosure) {.gcsafe.} proc reprArray(result: var string, p: pointer, typ: PNimType, cl: var ReprClosure) = @@ -188,7 +188,7 @@ when not defined(useNimRtl): add result, "]" proc reprRecordAux(result: var string, p: pointer, n: ptr TNimNode, - cl: var ReprClosure) {.benign.} = + cl: var ReprClosure) {.gcsafe.} = case n.kind of nkNone: sysAssert(false, "reprRecordAux") of nkSlot: diff --git a/lib/system/rwlocks.nim b/lib/system/rwlocks.nim new file mode 100644 index 0000000000..edc8a8f61a --- /dev/null +++ b/lib/system/rwlocks.nim @@ -0,0 +1,143 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2026 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# Read-write lock (RwLock) for lib/system. +# Used by YRC and by traceable containers that perform topology-changing ops. +# POSIX: pthread_rwlock_* ; Windows: SRWLOCK (slim reader/writer). + +{.push stackTrace: off.} + +when defined(windows): + # SRWLOCK is pointer-sized; use single pointer for ABI compatibility + type + RwLock* {.importc: "SRWLOCK", header: "", final, pure, byref.} = object + p: pointer + + proc initializeSRWLock(L: var RwLock) {.importc: "InitializeSRWLock", + header: "".} + proc acquireSRWLockShared(L: var RwLock) {.importc: "AcquireSRWLockShared", + header: "".} + proc releaseSRWLockShared(L: var RwLock) {.importc: "ReleaseSRWLockShared", + header: "".} + proc acquireSRWLockExclusive(L: var RwLock) {.importc: "AcquireSRWLockExclusive", + header: "".} + proc releaseSRWLockExclusive(L: var RwLock) {.importc: "ReleaseSRWLockExclusive", + header: "".} + + proc initRwLock*(L: var RwLock) {.inline.} = + initializeSRWLock(L) + proc deinitRwLock*(L: var RwLock) {.inline.} = + discard + proc acquireRead*(L: var RwLock) {.inline.} = + acquireSRWLockShared(L) + proc releaseRead*(L: var RwLock) {.inline.} = + releaseSRWLockShared(L) + proc acquireWrite*(L: var RwLock) {.inline.} = + acquireSRWLockExclusive(L) + proc releaseWrite*(L: var RwLock) {.inline.} = + releaseSRWLockExclusive(L) + +elif defined(genode): + {.error: "RwLock is not implemented for Genode".} + +else: + # POSIX: pthread_rwlock_* + type + SysRwLockObj {.importc: "pthread_rwlock_t", pure, final, + header: """#include + #include """, byref.} = object + when defined(linux) and defined(amd64): + abi: array[56 div sizeof(clong), clong] + + proc pthread_rwlock_init(rwlock: var SysRwLockObj, attr: pointer): cint {. + importc: "pthread_rwlock_init", header: "", noSideEffect.} + proc pthread_rwlock_destroy(rwlock: var SysRwLockObj): cint {. + importc: "pthread_rwlock_destroy", header: "", noSideEffect.} + proc pthread_rwlock_rdlock(rwlock: var SysRwLockObj): cint {. + importc: "pthread_rwlock_rdlock", header: "", noSideEffect.} + proc pthread_rwlock_wrlock(rwlock: var SysRwLockObj): cint {. + importc: "pthread_rwlock_wrlock", header: "", noSideEffect.} + proc pthread_rwlock_unlock(rwlock: var SysRwLockObj): cint {. + importc: "pthread_rwlock_unlock", header: "", noSideEffect.} + + when defined(linux): + # PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP: once a writer is waiting, + # new readers block. Prevents continuous mutator read-locks from starving + # the collector's write-lock acquisition (glibc default is PREFER_READER). + type + SysRwLockAttr {.importc: "pthread_rwlockattr_t", pure, final, + header: "".} = object + const PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP = cint(3) + proc pthread_rwlockattr_init(attr: ptr SysRwLockAttr): cint {. + importc: "pthread_rwlockattr_init", header: "".} + proc pthread_rwlockattr_destroy(attr: ptr SysRwLockAttr): cint {. + importc: "pthread_rwlockattr_destroy", header: "".} + proc pthread_rwlockattr_setkind_np(attr: ptr SysRwLockAttr; pref: cint): cint {. + importc: "pthread_rwlockattr_setkind_np", header: "".} + + when defined(ios): + type RwLock* = ptr SysRwLockObj + proc initRwLock*(L: var RwLock) = + when not declared(c_malloc): + proc c_malloc(size: csize_t): pointer {.importc: "malloc", header: "".} + proc c_free(p: pointer) {.importc: "free", header: "".} + L = cast[RwLock](c_malloc(csize_t(sizeof(SysRwLockObj)))) + discard pthread_rwlock_init(L[], nil) + proc deinitRwLock*(L: var RwLock) = + if L != nil: + discard pthread_rwlock_destroy(L[]) + when not declared(c_free): + proc c_free(p: pointer) {.importc: "free", header: "".} + c_free(L) + L = nil + proc acquireRead*(L: var RwLock) = + discard pthread_rwlock_rdlock(L[]) + proc releaseRead*(L: var RwLock) = + discard pthread_rwlock_unlock(L[]) + proc acquireWrite*(L: var RwLock) = + discard pthread_rwlock_wrlock(L[]) + proc releaseWrite*(L: var RwLock) = + discard pthread_rwlock_unlock(L[]) + else: + type RwLock* = SysRwLockObj + proc initRwLock*(L: var RwLock) = + when defined(linux): + var attr: SysRwLockAttr + discard pthread_rwlockattr_init(addr attr) + discard pthread_rwlockattr_setkind_np(addr attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP) + discard pthread_rwlock_init(L, addr attr) + discard pthread_rwlockattr_destroy(addr attr) + else: + discard pthread_rwlock_init(L, nil) + proc deinitRwLock*(L: var RwLock) = + discard pthread_rwlock_destroy(L) + proc acquireRead*(L: var RwLock) = + discard pthread_rwlock_rdlock(L) + proc releaseRead*(L: var RwLock) = + discard pthread_rwlock_unlock(L) + proc acquireWrite*(L: var RwLock) = + discard pthread_rwlock_wrlock(L) + proc releaseWrite*(L: var RwLock) = + discard pthread_rwlock_unlock(L) + +template withReadLock*(L: var RwLock, body: untyped) = + acquireRead(L) + try: + body + finally: + releaseRead(L) + +template withWriteLock*(L: var RwLock, body: untyped) = + acquireWrite(L) + try: + body + finally: + releaseWrite(L) + +{.pop.} diff --git a/lib/system/seqs_v2.nim b/lib/system/seqs_v2.nim index f0c880115c..154b443460 100644 --- a/lib/system/seqs_v2.nim +++ b/lib/system/seqs_v2.nim @@ -11,6 +11,90 @@ # import std/typetraits # strs already imported allocateds for us. +when defined(gcYrc): + include rwlocks + include threadids + + const + NumLockStripes = 64 + + type + YrcLockState = enum + HasNoLock + HasMutatorLock + HasCollectorLock + Collecting + + AlignedRwLock = object + ## One RwLock per cache line. {.align: 64.} causes the compiler to round + ## the struct size up to 64 bytes, so consecutive array elements never + ## share a cache line (sizeof(RwLock) = 56 on Linux x86_64 → 8 byte pad). + lock {.align: 64.}: RwLock + + var + gYrcLocks: array[NumLockStripes, AlignedRwLock] + var + lockState {.threadvar.}: YrcLockState + + proc getYrcStripe(): int {.inline.} = + ## Map this thread to one of the NumLockStripes RwLock stripes. + ## getThreadId() is already cached thread-locally in threadids.nim. + getThreadId() and (NumLockStripes - 1) + + proc acquireMutatorLock() {.compilerRtl, inl.} = + if lockState == HasNoLock: + acquireRead gYrcLocks[getYrcStripe()].lock + lockState = HasMutatorLock + + proc releaseMutatorLock() {.compilerRtl, inl.} = + if lockState == HasMutatorLock: + lockState = HasNoLock + releaseRead gYrcLocks[getYrcStripe()].lock + + template yrcMutatorLock*(t: typedesc; body: untyped) = + {.noSideEffect.}: + when canFormCycles(t): + acquireMutatorLock() + try: + body + finally: + {.noSideEffect.}: + when canFormCycles(t): + releaseMutatorLock() + + template yrcMutatorLockUntyped(body: untyped) = + {.noSideEffect.}: + acquireMutatorLock() + try: + body + finally: + {.noSideEffect.}: + releaseMutatorLock() + + template yrcCollectorLock(body: untyped) = + if lockState == HasMutatorLock: releaseMutatorLock() + let prevState = lockState + let hadToAcquire = prevState < HasCollectorLock + if hadToAcquire: + # Acquire all stripes in ascending order — the only thread ever holding + # multiple write locks is the collector, so there is no lock-order cycle. + for yrcI in 0..= x.len, "invalid newLen parameter for 'grow'" if newLen <= oldLen: return - var xu = cast[ptr NimSeqV2[T]](addr x) - if xu.p == nil or (xu.p.cap and not strlitFlag) < newLen: - xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, newLen - oldLen, sizeof(T), alignof(T))) - xu.len = newLen - for i in oldLen .. newLen-1: - when (NimMajor, NimMinor, NimPatch) >= (2, 3, 1): - xu.p.data[i] = `=dup`(value) - else: - wasMoved(xu.p.data[i]) - `=copy`(xu.p.data[i], value) + yrcMutatorLock(T): + var xu = cast[ptr NimSeqV2[T]](addr x) + if xu.p == nil or (xu.p.cap and not strlitFlag) < newLen: + xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, newLen - oldLen, sizeof(T), alignof(T))) + xu.len = newLen + for i in oldLen .. newLen-1: + when (NimMajor, NimMinor, NimPatch) >= (2, 3, 1): + xu.p.data[i] = `=dup`(value) + else: + wasMoved(xu.p.data[i]) + `=copy`(xu.p.data[i], value) proc add*[T](x: var seq[T]; y: sink T) {.magic: "AppendSeqElem", noSideEffect, nodestroy.} = ## Generic proc for adding a data item `y` to a container `x`. @@ -152,30 +238,32 @@ proc add*[T](x: var seq[T]; y: sink T) {.magic: "AppendSeqElem", noSideEffect, n ## Generic code becomes much easier to write if the Nim naming scheme is ## respected. {.cast(noSideEffect).}: - let oldLen = x.len - var xu = cast[ptr NimSeqV2[T]](addr x) - if xu.p == nil or (xu.p.cap and not strlitFlag) < oldLen+1: - xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, 1, sizeof(T), alignof(T))) - xu.len = oldLen+1 - # .nodestroy means `xu.p.data[oldLen] = value` is compiled into a - # copyMem(). This is fine as know by construction that - # in `xu.p.data[oldLen]` there is nothing to destroy. - # We also save the `wasMoved + destroy` pair for the sink parameter. - xu.p.data[oldLen] = y + yrcMutatorLock(T): + let oldLen = x.len + var xu = cast[ptr NimSeqV2[T]](addr x) + if xu.p == nil or (xu.p.cap and not strlitFlag) < oldLen+1: + xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, 1, sizeof(T), alignof(T))) + xu.len = oldLen+1 + # .nodestroy means `xu.p.data[oldLen] = value` is compiled into a + # copyMem(). This is fine as know by construction that + # in `xu.p.data[oldLen]` there is nothing to destroy. + # We also save the `wasMoved + destroy` pair for the sink parameter. + xu.p.data[oldLen] = y proc setLen[T](s: var seq[T], newlen: Natural) {.nodestroy.} = {.noSideEffect.}: if newlen < s.len: shrink(s, newlen) else: - let oldLen = s.len - if newlen <= oldLen: return - var xu = cast[ptr NimSeqV2[T]](addr s) - if xu.p == nil or (xu.p.cap and not strlitFlag) < newlen: - xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, newlen - oldLen, sizeof(T), alignof(T))) - xu.len = newlen - for i in oldLen..= 0, calls `scanBlack`, and rescues A -# and everything reachable from it — including B. In short: the mutator can only -# modify objects it can reach, but the cycle collector only frees objects nothing -# external can reach. The two conditions are mutually exclusive. +# This problem structurally cannot arise in YRC for two reasons: # -#[ - -The problem described in Bacon01 is: during markGray/scan, a mutator concurrently -does X.field = Z (was X→Y), changing the physical graph while the collector is tracing -it. The collector might see stale or new edges. The reasons this is still safe: - -Stale edges cancel with unbuffered decrements: If the collector sees old edge X→Y -(mutator already wrote X→Z and buffered dec(Y)), the phantom trial deletion and the -unbuffered dec cancel — Y's effective RC is correct. - -scanBlack rescues via current physical edges: If X has external refs (merged RC reflects -the mutator's access), scanBlack(X) re-traces X and follows the current physical edge X→Z, -incrementing Z's RC and marking it black. Z survives. - -rcSum==edges fast path is conservative: Any discrepancy between physical graph and merged -state (stale or new edges) causes rcSum != edges, falling back to the slow path which -rescues anything with RC >= 0. - -Unreachable cycles are truly unreachable: The mutator can only reach objects through chains -rooted in merged references. If a cycle has zero external refs at merge time, no mutator -can reach it. - -]# +# 1. The mutator lock freezes the topology during all three passes, so no +# concurrent field write can race with markGray/scan/collectWhite. +# +# 2. Even without the lock, the cycle collector only frees *closed cycles* — +# subgraphs where every reference to every member comes from within the +# group, with zero external references. To execute `A.field = B` the +# mutator must hold a reference to A (external ref), which `scan` would +# rescue. The two conditions are mutually exclusive. +# +# In practice reason (1) makes reason (2) a belt-and-suspenders safety +# argument rather than the primary mechanism. {.push raises: [].} @@ -90,15 +68,52 @@ const logOrc = defined(nimArcIds) type - TraceProc = proc (p, env: pointer) {.nimcall, benign, raises: [].} - DisposeProc = proc (p: pointer) {.nimcall, benign, raises: [].} + TraceProc = proc (p, env: pointer) {.nimcall, gcsafe, raises: [].} + DisposeProc = proc (p: pointer) {.nimcall, gcsafe, raises: [].} -template color(c): untyped = c.rc and colorMask -template setColor(c, col) = - when col == colBlack: - c.rc = c.rc and not colorMask - else: - c.rc = c.rc and not colorMask or col +when defined(nimYrcAtomicIncs): + template color(c): untyped = atomicLoadN(addr c.rc, ATOMIC_ACQUIRE) and colorMask + template setColor(c, col) = + block: + var expected = atomicLoadN(addr c.rc, ATOMIC_RELAXED) + while true: + let desired = (expected and not colorMask) or col + if atomicCompareExchangeN(addr c.rc, addr expected, desired, true, + ATOMIC_ACQ_REL, ATOMIC_RELAXED): + break + template loadRc(c): int = atomicLoadN(addr c.rc, ATOMIC_ACQUIRE) + template trialDec(c) = + discard atomicFetchAdd(addr c.rc, -rcIncrement, ATOMIC_ACQ_REL) + template trialInc(c) = + discard atomicFetchAdd(addr c.rc, rcIncrement, ATOMIC_ACQ_REL) + template rcClearFlag(c, flag) = + block: + var expected = atomicLoadN(addr c.rc, ATOMIC_RELAXED) + while true: + let desired = expected and not flag + if atomicCompareExchangeN(addr c.rc, addr expected, desired, true, + ATOMIC_ACQ_REL, ATOMIC_RELAXED): + break + template rcSetFlag(c, flag) = + block: + var expected = atomicLoadN(addr c.rc, ATOMIC_RELAXED) + while true: + let desired = expected or flag + if atomicCompareExchangeN(addr c.rc, addr expected, desired, true, + ATOMIC_ACQ_REL, ATOMIC_RELAXED): + break +else: + template color(c): untyped = c.rc and colorMask + template setColor(c, col) = + when col == colBlack: + c.rc = c.rc and not colorMask + else: + c.rc = c.rc and not colorMask or col + template loadRc(c): int = c.rc + template trialDec(c) = c.rc = c.rc -% rcIncrement + template trialInc(c) = c.rc = c.rc +% rcIncrement + template rcClearFlag(c, flag) = c.rc = c.rc and not flag + template rcSetFlag(c, flag) = c.rc = c.rc or flag const optimizedOrc = false @@ -118,8 +133,6 @@ proc trace(s: Cell; desc: PNimTypeV2; j: var GcEnv) {.inline.} = var p = s +! sizeof(RefHeader) cast[TraceProc](desc.traceImpl)(p, addr(j)) -include threadids - type Stripe = object when not defined(yrcAtomics): @@ -131,13 +144,12 @@ type toDec: array[QueueSize, (Cell, PNimTypeV2)] type - PreventThreadFromCollectProc* = proc(): bool {.nimcall, benign, raises: [].} + PreventThreadFromCollectProc* = proc(): bool {.nimcall, gcsafe, raises: [].} ## Callback run before this thread runs the cycle collector. ## Return `true` to allow collection, `false` to skip (e.g. real-time thread). ## Invoked while holding the global lock; must not call back into YRC. var - gYrcGlobalLock: Lock roots: CellSeq[Cell] # merged roots, used under global lock stripes: array[NumStripes, Stripe] rootsThreshold: int = 128 @@ -182,13 +194,15 @@ proc nimIncRefCyclic(p: pointer; cyclic: bool) {.compilerRtl, inl.} = let h = head(p) when optimizedOrc: if cyclic: h.rc = h.rc or maybeCycle - when defined(yrcAtomics): + when defined(nimYrcAtomicIncs): + discard atomicFetchAdd(addr h.rc, rcIncrement, ATOMIC_ACQ_REL) + elif defined(yrcAtomics): let s = getStripeIdx() let slot = atomicFetchAdd(addr stripes[s].toIncLen, 1, ATOMIC_ACQ_REL) if slot < QueueSize: atomicStoreN(addr stripes[s].toInc[slot], h, ATOMIC_RELEASE) else: - withLock gYrcGlobalLock: + yrcCollectorLock: h.rc = h.rc +% rcIncrement for i in 0.. until: let (entry, desc) = j.traceStack.pop() let t = head entry[] - t.rc = t.rc +% rcIncrement + trialInc(t) if t.color != colBlack: t.setColor colBlack trace(t, desc, j) @@ -301,23 +317,23 @@ proc markGray(s: Cell; desc: PNimTypeV2; j: var GcEnv) = if s.color != colGray: s.setColor colGray j.touched = j.touched +% 1 - j.rcSum = j.rcSum +% (s.rc shr rcShift) +% 1 + j.rcSum = j.rcSum +% (loadRc(s) shr rcShift) +% 1 orcAssert(j.traceStack.len == 0, "markGray: trace stack not empty") trace(s, desc, j) while j.traceStack.len > 0: let (entry, desc) = j.traceStack.pop() let t = head entry[] - t.rc = t.rc -% rcIncrement + trialDec(t) j.edges = j.edges +% 1 if t.color != colGray: t.setColor colGray j.touched = j.touched +% 1 - j.rcSum = j.rcSum +% (t.rc shr rcShift) +% 2 + j.rcSum = j.rcSum +% (loadRc(t) shr rcShift) +% 2 trace(t, desc, j) proc scan(s: Cell; desc: PNimTypeV2; j: var GcEnv) = if s.color == colGray: - if (s.rc shr rcShift) >= 0: + if (loadRc(s) shr rcShift) >= 0: scanBlack(s, desc, j) else: orcAssert(j.traceStack.len == 0, "scan: trace stack not empty") @@ -327,14 +343,14 @@ proc scan(s: Cell; desc: PNimTypeV2; j: var GcEnv) = let (entry, desc) = j.traceStack.pop() let t = head entry[] if t.color == colGray: - if (t.rc shr rcShift) >= 0: + if (loadRc(t) shr rcShift) >= 0: scanBlack(t, desc, j) else: t.setColor(colWhite) trace(t, desc, j) proc collectColor(s: Cell; desc: PNimTypeV2; col: int; j: var GcEnv) = - if s.color == col and (s.rc and inRootsFlag) == 0: + if s.color == col and (loadRc(s) and inRootsFlag) == 0: orcAssert(j.traceStack.len == 0, "collectWhite: trace stack not empty") s.setColor(colBlack) j.toFree.add(s, desc) @@ -343,7 +359,7 @@ proc collectColor(s: Cell; desc: PNimTypeV2; col: int; j: var GcEnv) = let (entry, desc) = j.traceStack.pop() let t = head entry[] entry[] = nil - if t.color == col and (t.rc and inRootsFlag) == 0: + if t.color == col and (loadRc(t) and inRootsFlag) == 0: j.toFree.add(t, desc) t.setColor(colBlack) trace(t, desc, j) @@ -351,6 +367,9 @@ proc collectColor(s: Cell; desc: PNimTypeV2; col: int; j: var GcEnv) = proc collectCyclesBacon(j: var GcEnv; lowMark: int) = # YRC defers all destruction to collection time - process ALL roots through Bacon's algorithm # This is different from ORC which handles immediate garbage (rc == 0) directly + if lockState == Collecting: + return + lockState = Collecting let last = roots.len -% 1 when logOrc: for i in countdown(last, lowMark): @@ -374,13 +393,10 @@ proc collectCyclesBacon(j: var GcEnv; lowMark: int) = init j.toFree for i in 0 ..< roots.len: let s = roots.d[i][0] - s.rc = s.rc and not inRootsFlag + rcClearFlag(s, inRootsFlag) collectColor(s, roots.d[i][1], colToCollect, j) # Clear roots before freeing to prevent nested collectCycles() from accessing freed cells - when not defined(nimStressOrc): - let oldThreshold = rootsThreshold - rootsThreshold = high(int) roots.len = 0 # Free all collected objects @@ -390,8 +406,6 @@ proc collectCyclesBacon(j: var GcEnv; lowMark: int) = when orcLeakDetector: writeCell("CYCLIC OBJECT FREED", s, j.toFree.d[i][1]) free(s, j.toFree.d[i][1]) - when not defined(nimStressOrc): - rootsThreshold = oldThreshold j.freed = j.freed +% j.toFree.len deinit j.toFree @@ -401,7 +415,7 @@ when defined(nimOrcStats): proc collectCycles() = when logOrc: cfprintf(cstderr, "[collectCycles] begin\n") - withLock gYrcGlobalLock: + yrcCollectorLock: mergePendingRoots() if roots.len >= RootsThreshold and mayRunCycleCollect(): var j: GcEnv @@ -436,9 +450,9 @@ when defined(nimOrcStats): result = OrcStats(freedCyclicObjects: freedCyclicObjects) proc GC_runOrc* = - withLock gYrcGlobalLock: + yrcCollectorLock: mergePendingRoots() - if mayRunCycleCollect(): + if roots.len > 0 and mayRunCycleCollect(): var j: GcEnv init j.traceStack collectCyclesBacon(j, 0) @@ -455,12 +469,12 @@ proc GC_disableOrc*() = rootsThreshold = high(int) proc GC_prepareOrc*(): int {.inline.} = - withLock gYrcGlobalLock: + yrcCollectorLock: mergePendingRoots() result = roots.len proc GC_partialCollect*(limit: int) = - withLock gYrcGlobalLock: + yrcCollectorLock: mergePendingRoots() if roots.len > limit and mayRunCycleCollect(): var j: GcEnv @@ -536,22 +550,24 @@ proc yrcDec(tmp: pointer; desc: PNimTypeV2) {.inline.} = proc nimAsgnYrc(dest: ptr pointer; src: pointer; desc: PNimTypeV2) {.compilerRtl.} = ## YRC write barrier for ref copy assignment. - ## Atomically stores src into dest, then buffers RC adjustments. - ## Freeing is always done by the cycle collector, never inline. + ## Holds the mutator read lock for the entire operation so the collector + ## cannot run between the incRef and decRef, closing the stale-decRef + ## bug. Direct atomic incRef replaces the toInc stripe queue: the + ## collector is blocked, so the RC update is immediately visible and correct. + acquireMutatorLock() + if src != nil: increment head(src) # direct atomic: no toInc queue needed let tmp = dest[] - atomicStoreN(dest, src, ATOMIC_RELEASE) - if src != nil: - nimIncRefCyclic(src, true) - if tmp != nil: - yrcDec(tmp, desc) + dest[] = src + if tmp != nil: yrcDec(tmp, desc) # still deferred via toDec for cycle detection + releaseMutatorLock() proc nimSinkYrc(dest: ptr pointer; src: pointer; desc: PNimTypeV2) {.compilerRtl.} = ## YRC write barrier for ref sink (move). No incRef on source. - ## Freeing is always done by the cycle collector, never inline. + acquireMutatorLock() let tmp = dest[] - atomicStoreN(dest, src, ATOMIC_RELEASE) - if tmp != nil: - yrcDec(tmp, desc) + dest[] = src + if tmp != nil: yrcDec(tmp, desc) + releaseMutatorLock() proc nimMarkCyclic(p: pointer) {.compilerRtl, inl.} = when optimizedOrc: @@ -559,10 +575,12 @@ proc nimMarkCyclic(p: pointer) {.compilerRtl, inl.} = let h = head(p) h.rc = h.rc or maybeCycle -# Initialize locks at module load -initLock(gYrcGlobalLock) +# Initialize locks at module load. +# RwLock stripes live in seqs_v2 (gYrcLocks); NumLockStripes is exported from there. +for i in 0.. SeqPayloads \cup {NULL}] -- current payload for obj's seq + payloadAlive, \* [SeqPayloads -> BOOLEAN] -- is this payload's memory valid? + \* RWLock read side: set of threads holding the read lock. + \* Seq mutations (assign, add, setLen, etc.) acquire the read lock. + \* The collector (write lock holder) gets exclusive access. + rwLockReaders, \* SUBSET Threads -- threads currently holding the read lock + \* Collector's in-progress seq trace: the payload pointer read during tracing. + \* Between reading the pointer and accessing the data, the payload could be freed. + collectorPayload \* SeqPayloads \cup {NULL} -- payload being traced by collector + +\* Convenience tuple for seq-related variables (used in UNCHANGED clauses) +seqVars == <> \* Type invariants TypeOK == @@ -117,6 +161,11 @@ TypeOK == /\ mergedRoots \in Seq([obj: Objects, desc: ObjTypes]) /\ collecting \in BOOLEAN /\ pendingWrites \in SUBSET ([thread: Threads, dest: Objects, old: Objects \cup {NULL}, src: Objects \cup {NULL}, phase: {"store", "inc", "dec"}]) + \* Seq payload types + /\ seqData \in [Objects -> SeqPayloads \cup {NULL}] + /\ payloadAlive \in [SeqPayloads -> BOOLEAN] + /\ rwLockReaders \in SUBSET Threads + /\ collectorPayload \in SeqPayloads \cup {NULL} \* Helper: internal reference count (heap-to-heap edges) InternalRC(obj) == @@ -163,7 +212,7 @@ MutatorWriteAtomicStore(thread, destObj, destField, oldVal, newVal, desc) == IF x = newVal /\ newVal # NULL THEN TRUE ELSE FALSE]] - /\ UNCHANGED <> + /\ UNCHANGED <> \* ============================================================================ \* Phase 2: RC Buffering (if space available) @@ -193,7 +242,7 @@ WriteBarrier(thread, destObj, destField, oldVal, newVal, desc) == /\ toDec' = IF oldVal # NULL /\ toDecLen[stripe] < QueueSize THEN [toDec EXCEPT ![stripe] = Append(toDec[stripe], [obj |-> oldVal, desc |-> desc])] ELSE toDec - /\ UNCHANGED <> + /\ UNCHANGED <> \* ============================================================================ \* Phase 3: Overflow Handling (separate actions that can block) @@ -215,7 +264,7 @@ MutatorWriteMergeInc(thread) == externalRC == Cardinality({t \in Threads : roots[t][x]}) IN internalRC + externalRC] /\ globalLock' = NULL \* Release lock after merge - /\ UNCHANGED <> + /\ UNCHANGED <> \* Handle decrement overflow: merge ALL buffers when lock is available \* This calls collectCycles() which merges both increment and decrement buffers @@ -257,7 +306,7 @@ MutatorWriteMergeDec(thread) == /\ toDecLen' = [s \in 0..(NumStripes-1) |-> 0] /\ toDec' = [s \in 0..(NumStripes-1) |-> <<>>] /\ globalLock' = NULL \* Lock acquired, merge done, lock released (entire withLock block is atomic) - /\ UNCHANGED <> + /\ UNCHANGED <> \* ============================================================================ \* Merge Operation: mergePendingRoots @@ -311,7 +360,7 @@ MergePendingRoots == /\ toInc' = [s \in 0..(NumStripes-1) |-> <<>>] /\ toDecLen' = [s \in 0..(NumStripes-1) |-> 0] /\ toDec' = [s \in 0..(NumStripes-1) |-> <<>>] - /\ UNCHANGED <> + /\ UNCHANGED <> \* ============================================================================ \* Trial Deletion: markGray @@ -365,7 +414,7 @@ MarkGray(obj, desc) == \* For roots, the RC includes external refs which survive trial deletion. rc' = [x \in Objects |-> IF x \in allReachable THEN rc[x] - internalEdgeCount[x] ELSE rc[x]] - /\ UNCHANGED <> + /\ UNCHANGED <> \* ============================================================================ \* Scan Phase @@ -422,7 +471,7 @@ Scan(obj, desc) == ELSE \* Mark white (part of closed cycle) /\ color' = [color EXCEPT ![obj] = colWhite] /\ UNCHANGED <> - /\ UNCHANGED <> + /\ UNCHANGED <> \* ============================================================================ \* Collection Phase: collectColor @@ -442,7 +491,7 @@ CollectColor(obj, desc, targetColor) == edges' = [edges EXCEPT ![obj] = [x \in Objects |-> IF x = obj THEN FALSE ELSE edges[obj][x]]] /\ color' = [color EXCEPT ![obj] = colBlack] \* Mark as freed - /\ UNCHANGED <> + /\ UNCHANGED <> \* ============================================================================ \* Collection Cycle: collectCyclesBacon @@ -454,7 +503,7 @@ StartCollection == /\ Len(mergedRoots) >= RootsThreshold /\ collecting' = TRUE /\ gcEnv' = [touched |-> 0, edges |-> 0, rcSum |-> 0, toFree |-> {}] - /\ UNCHANGED <> + /\ UNCHANGED <> EndCollection == /\ globalLock # NULL @@ -464,7 +513,7 @@ EndCollection == IF x \in {r.obj : r \in mergedRoots} THEN FALSE ELSE inRoots[x]] /\ mergedRoots' = <<>> /\ collecting' = FALSE - /\ UNCHANGED <> + /\ UNCHANGED <> \* ============================================================================ \* Mutator Actions @@ -491,7 +540,7 @@ MutatorWrite(thread, destObj, destField, oldVal, newVal, desc) == IF incOverflow \/ decOverflow THEN \* Overflow: atomic store happened, but buffering is deferred \* Buffers stay full, merge will happen when lock is available (via MutatorWriteMergeInc/Dec) - /\ UNCHANGED <> + /\ UNCHANGED <> ELSE \* No overflow: buffer normally /\ WriteBarrier(thread, destObj, destField, oldVal, newVal, desc) /\ UNCHANGED <> @@ -521,16 +570,19 @@ MutatorRootAssign(thread, obj, val) == /\ collecting' = collecting /\ gcEnv' = gcEnv /\ pendingWrites' = pendingWrites + /\ UNCHANGED seqVars \* ============================================================================ \* Collector Actions \* ============================================================================ -\* Collector acquires global lock for entire collection cycle +\* Collector acquires write lock (global lock) for entire collection cycle. +\* RWLock semantics: writer can only acquire when no readers hold the read lock. CollectorAcquireLock(thread) == /\ globalLock = NULL + /\ rwLockReaders = {} \* RWLock: no readers allowed when acquiring write lock /\ globalLock' = thread - /\ UNCHANGED <> + /\ UNCHANGED <> CollectorMerge == /\ globalLock # NULL @@ -571,7 +623,93 @@ CollectorEnd == CollectorReleaseLock(thread) == /\ globalLock = thread /\ globalLock' = NULL - /\ UNCHANGED <> + /\ UNCHANGED <> + +\* ============================================================================ +\* Seq Payload Actions (RWLock-protected) +\* ============================================================================ +\* These actions model the race between the collector tracing seq payloads +\* and mutators replacing/freeing seq payloads. +\* +\* The collector traces seq payloads in two steps: +\* 1. CollectorStartTraceSeq: reads seqData[obj] (gets payload pointer) +\* 2. CollectorFinishTraceSeq: accesses the payload data +\* Between these steps, a mutator could free the payload (the race). +\* +\* The RWLock prevents this: +\* - Collector holds write lock (globalLock) during tracing +\* - MutatorSeqAssign requires read lock (rwLockReaders) +\* - Read lock requires globalLock = NULL +\* - Therefore MutatorSeqAssign is blocked during collection +\* +\* Note: This models the memory safety aspect of seq tracing. +\* The cycle collection algorithm (MarkGray, Scan, etc.) operates on the +\* logical edge graph. Seq payloads are a physical representation detail +\* that affects memory safety but not GC correctness (which is already +\* covered by the existing Safety property). + +\* Mutator acquires read lock for seq mutation. +\* RWLock semantics: read lock can be acquired when no writer holds the write lock. +\* Multiple readers can hold the read lock simultaneously. +MutatorAcquireSeqLock(thread) == + /\ globalLock = NULL \* RWLock: no writer allowed when acquiring read lock + /\ thread \notin rwLockReaders + /\ rwLockReaders' = rwLockReaders \cup {thread} + /\ UNCHANGED <> + +\* Mutator releases read lock after seq mutation completes. +MutatorReleaseSeqLock(thread) == + /\ thread \in rwLockReaders + /\ rwLockReaders' = rwLockReaders \ {thread} + /\ UNCHANGED <> + +\* Mutator replaces a seq field's payload (e.g., r.list = newSeq). +\* This frees the old payload and installs a new one. +\* Requires the read lock (RWLock protection against concurrent collection). +\* +\* In the real implementation, this is a value-type assignment (=sink/=copy) +\* that frees the old data array and installs a new one. The old array is freed +\* immediately, NOT deferred to the cycle collector. +MutatorSeqAssign(thread, obj, newPayload) == + /\ thread \in rwLockReaders \* Must hold read lock + /\ seqData[obj] # NULL \* Object has an existing seq payload + /\ newPayload \in SeqPayloads + /\ ~payloadAlive[newPayload] \* New payload is freshly allocated (not yet alive) + /\ LET oldPayload == seqData[obj] + IN + /\ seqData' = [seqData EXCEPT ![obj] = newPayload] + /\ payloadAlive' = [payloadAlive EXCEPT ![oldPayload] = FALSE, + ![newPayload] = TRUE] + \* Note: In a complete model, this would also update edges[obj] to reflect + \* the new seq elements and buffer RC changes (inc new elements, dec old elements). + \* We omit this here to focus on the memory safety property (payload lifetime). + /\ UNCHANGED <> + +\* Collector begins tracing an object's seq field. +\* Reads the seqData pointer and stores it in collectorPayload. +\* This is the first step of a two-step trace operation. +\* The collector must hold the write lock (globalLock). +CollectorStartTraceSeq(obj) == + /\ globalLock # NULL \* Collector holds write lock + /\ collecting = TRUE \* In collection phase + /\ seqData[obj] # NULL \* Object has a seq field + /\ collectorPayload = NULL \* Not already mid-trace + /\ collectorPayload' = seqData[obj] + /\ UNCHANGED <> + +\* Collector finishes tracing an object's seq field. +\* Accesses the payload data via collectorPayload. +\* The payload MUST still be alive (this is checked by SeqPayloadSafety). +\* After accessing the payload, clears collectorPayload. +CollectorFinishTraceSeq == + /\ globalLock # NULL \* Collector holds write lock + /\ collecting = TRUE \* In collection phase + /\ collectorPayload # NULL \* Mid-trace on a payload + \* The actual work: read payloadEdges[collectorPayload] to discover children. + \* We don't model the trace results here; the safety property ensures + \* the read is valid (payload is alive). + /\ collectorPayload' = NULL + /\ UNCHANGED <> \* ============================================================================ \* Next State Relation @@ -607,6 +745,16 @@ Next == \/ CollectorEnd \/ \E thread \in Threads: CollectorReleaseLock(thread) + \* --- Seq payload actions --- + \/ \E thread \in Threads: + MutatorAcquireSeqLock(thread) + \/ \E thread \in Threads: + MutatorReleaseSeqLock(thread) + \/ \E thread \in Threads, obj \in Objects, p \in SeqPayloads: + MutatorSeqAssign(thread, obj, p) + \/ \E obj \in Objects: + CollectorStartTraceSeq(obj) + \/ CollectorFinishTraceSeq \* ============================================================================ \* Initial State @@ -642,6 +790,11 @@ Init == /\ collecting = FALSE /\ gcEnv = [touched |-> 0, edges |-> 0, rcSum |-> 0, toFree |-> {}] /\ pendingWrites = {} + \* Seq payload initial state + /\ seqData = [x \in Objects |-> NULL] \* No seq fields initially + /\ payloadAlive = [p \in SeqPayloads |-> FALSE] \* No payloads alive initially + /\ rwLockReaders = {} \* No threads hold read lock + /\ collectorPayload = NULL \* Collector not mid-trace /\ TypeOK \* ============================================================================ @@ -748,14 +901,53 @@ CycleInvariant == THEN ExternalRC(obj) = 0 ELSE TRUE +\* ============================================================================ +\* Seq Payload Safety +\* ============================================================================ +\* Memory safety: The collector never accesses a freed seq payload. +\* +\* collectorPayload holds the payload pointer the collector read during +\* CollectorStartTraceSeq. Between that action and CollectorFinishTraceSeq, +\* the collector will dereference this pointer to read the seq's elements. +\* If the payload has been freed in between, this is a use-after-free. +\* +\* The RWLock prevents this: +\* - collectorPayload is only set when globalLock # NULL (write lock held) +\* - MutatorSeqAssign (which frees payloads) requires rwLockReaders membership +\* - MutatorAcquireSeqLock requires globalLock = NULL (no writer) +\* - Therefore: while collectorPayload # NULL, no MutatorSeqAssign can execute +\* - Therefore: payloadAlive[collectorPayload] remains TRUE +\* +\* Without the RWLock (if MutatorSeqAssign didn't require the read lock), +\* the following interleaving would violate this property: +\* 1. Collector acquires write lock +\* 2. CollectorStartTraceSeq(obj) -- collectorPayload = P +\* 3. MutatorSeqAssign(thread, obj, Q) -- frees P, payloadAlive[P] = FALSE +\* 4. SeqPayloadSafety VIOLATED: collectorPayload = P but payloadAlive[P] = FALSE + +SeqPayloadSafety == + collectorPayload # NULL => payloadAlive[collectorPayload] + +\* ============================================================================ +\* RWLock Invariant +\* ============================================================================ +\* The read-write lock ensures mutual exclusion between the collector (writer) +\* and seq mutations (readers). The writer and readers are never active at +\* the same time. + +RWLockInvariant == + globalLock # NULL => rwLockReaders = {} + \* ============================================================================ \* Specification \* ============================================================================ -Spec == Init /\ [][Next]_<> +Spec == Init /\ [][Next]_<> THEOREM Spec => []Safety THEOREM Spec => []RCInvariant THEOREM Spec => []CycleInvariant +THEOREM Spec => []SeqPayloadSafety +THEOREM Spec => []RWLockInvariant ==== diff --git a/tests/arc/torcbench.nim b/tests/arc/torcbench.nim index 4c9e65feea..e54537883a 100644 --- a/tests/arc/torcbench.nim +++ b/tests/arc/torcbench.nim @@ -35,4 +35,5 @@ proc main() = main() GC_fullCollect() -echo getOccupiedMem() < 10 * 1024 * 1024, " peak memory: ", getMaxMem() < 10 * 1024 * 1024 +when not defined(useMalloc): + echo getOccupiedMem() < 10 * 1024 * 1024, " peak memory: ", getMaxMem() < 10 * 1024 * 1024 diff --git a/tests/codegen/titaniummangle_nim.nim b/tests/codegen/titaniummangle_nim.nim index 204d6ac063..26953166e1 100644 --- a/tests/codegen/titaniummangle_nim.nim +++ b/tests/codegen/titaniummangle_nim.nim @@ -1,35 +1,7 @@ discard """ targets: "c" matrix: "--debugger:native --mangle:nim" - ccodecheck: "'testFunc__titaniummangle95nim_u1316'" - ccodecheck: "'testFunc__titaniummangle95nim_u156'" - ccodecheck: "'testFunc__titaniummangle95nim_u1305'" - ccodecheck: "'testFunc__titaniummangle95nim_u241'" - ccodecheck: "'testFunc__titaniummangle95nim_u1357'" - ccodecheck: "'testFunc__titaniummangle95nim_u292'" - ccodecheck: "'testFunc__titaniummangle95nim_u38'" - ccodecheck: "'testFunc__titaniummangle95nim_u175'" - ccodecheck: "'testFunc__titaniummangle95nim_u1302'" - ccodecheck: "'testFunc__titaniummangle95nim_u1305'" - ccodecheck: "'testFunc__titaniummangle95nim_u535'" - ccodecheck: "'testFunc__titaniummangle95nim_u1294'" - ccodecheck: "'testFunc__titaniummangle95nim_u336'" - ccodecheck: "'testFunc__titaniummangle95nim_u425'" - ccodecheck: "'testFunc__titaniummangle95nim_u308'" - ccodecheck: "'testFunc__titaniummangle95nim_u129'" - ccodecheck: "'testFunc__titaniummangle95nim_u320'" - ccodecheck: "'testFunc__titaniummangle95nim_u223'" - ccodecheck: "'testFunc__titaniummangle95nim_u545'" - ccodecheck: "'testFunc__titaniummangle95nim_u543'" - ccodecheck: "'testFunc__titaniummangle95nim_u895'" - ccodecheck: "'testFunc__titaniummangle95nim_u1104'" - ccodecheck: "'testFunc__titaniummangle95nim_u1155'" - ccodecheck: "'testFunc__titaniummangle95nim_u636'" - ccodecheck: "'testFunc__titaniummangle95nim_u705'" - ccodecheck: "'testFunc__titaniummangle95nim_u800'" - ccodecheck: "'new__titaniummangle95nim_u1320'" - ccodecheck: "'xxx__titaniummangle95nim_u1391'" - ccodecheck: "'xxx__titaniummangle95nim_u1394'" + ccodecheck: "'testFunc__titaniummangle95nim_u'" """ #When debugging this notice that if one check fails, it can be due to any of the above. @@ -48,7 +20,7 @@ type Container[T] = object data: T - + Container2[T, T2] = object data: T data2: T2 @@ -57,7 +29,7 @@ type Coo = Foo - Doo = Boo | Foo + Doo = Boo | Foo TestProc = proc(a:string): string @@ -67,87 +39,87 @@ type EnumSample = enum type EnumAnotherSample = enum a, b, c -proc testFunc(a: set[EnumSample]) = +proc testFunc(a: set[EnumSample]) = echo $a -proc testFunc(a: typedesc) = +proc testFunc(a: typedesc) = echo $a -proc testFunc(a: ptr Foo) = +proc testFunc(a: ptr Foo) = echo repr a -proc testFunc(s: string, a: Coo) = +proc testFunc(s: string, a: Coo) = echo repr a -proc testFunc(s: int, a: Comparable) = +proc testFunc(s: int, a: Comparable) = echo repr a -proc testFunc(a: TestProc) = +proc testFunc(a: TestProc) = let b = "" echo repr a("") -proc testFunc(a: ref Foo) = +proc testFunc(a: ref Foo) = echo repr a -proc testFunc(b: Boo) = +proc testFunc(b: Boo) = echo repr b -proc testFunc(a: ptr UncheckedArray[int]) = +proc testFunc(a: ptr UncheckedArray[int]) = echo repr a -proc testFunc(a: ptr int) = +proc testFunc(a: ptr int) = echo repr a -proc testFunc(a: ptr ptr int) = +proc testFunc(a: ptr ptr int) = echo repr a -proc testFunc(e: FooTuple, str: cstring) = +proc testFunc(e: FooTuple, str: cstring) = echo e -proc testFunc(e: (float, float)) = +proc testFunc(e: (float, float)) = echo e -proc testFunc(e: EnumSample) = +proc testFunc(e: EnumSample) = echo e -proc testFunc(e: var int) = +proc testFunc(e: var int) = echo e -proc testFunc(e: var Foo, a, b: int32, refFoo: ref Foo) = +proc testFunc(e: var Foo, a, b: int32, refFoo: ref Foo) = echo e -proc testFunc(xs: Container[int]) = +proc testFunc(xs: Container[int]) = let a = 2 echo xs -proc testFunc(xs: Container2[int32, int32]) = +proc testFunc(xs: Container2[int32, int32]) = let a = 2 echo xs -proc testFunc(xs: Container[Container2[int32, int32]]) = +proc testFunc(xs: Container[Container2[int32, int32]]) = let a = 2 echo xs -proc testFunc(xs: seq[int]) = +proc testFunc(xs: seq[int]) = let a = 2 echo xs -proc testFunc(xs: openArray[string]) = +proc testFunc(xs: openArray[string]) = let a = 2 echo xs -proc testFunc(xs: array[2, int]) = +proc testFunc(xs: array[2, int]) = let a = 2 echo xs -proc testFunc(e: EnumAnotherSample) = +proc testFunc(e: EnumAnotherSample) = echo e -proc testFunc(a, b: int) = +proc testFunc(a, b: int) = echo "hola" discard -proc testFunc(a: int, xs: varargs[string]) = +proc testFunc(a: int, xs: varargs[string]) = let a = 10 for x in xs: echo x @@ -155,7 +127,7 @@ proc testFunc(a: int, xs: varargs[string]) = proc xxx(v: static int) = echo v -proc testFunc() = +proc testFunc() = var a = 2 var aPtr = a.addr var foo = Foo() diff --git a/tests/generics/tgenerics_issues.nim b/tests/generics/tgenerics_issues.nim index da202874e1..e865c8f7be 100644 --- a/tests/generics/tgenerics_issues.nim +++ b/tests/generics/tgenerics_issues.nim @@ -902,3 +902,15 @@ block: # issue #25494 a, b, c foo[MyEnum]() + +block: # issue #25005 + type + RpcResponse[T] = ref object + result: T + + func testit[T](p: var ref T) = + p = new(T) + + var v: RpcResponse[string] + testit(v) + diff --git a/tests/generics/tself_type.nim b/tests/generics/tself_type.nim new file mode 100644 index 0000000000..c108fc594a --- /dev/null +++ b/tests/generics/tself_type.nim @@ -0,0 +1,48 @@ +# issue 16754 + +type + Opt[T] = object + when T is ref: + val: T + x: int + else: + val: T + x: string + +type + Foo = ref object + x: Opt[Foo] + + Bar = object + x: ref Opt[Bar] + +var f = Foo() +assert f.x.x is int +var b = Bar() +assert b.x.x is string + +type + BazG[T] = object + x: int + + BazGRef[T] = ref object + x: T + + Baz = object + x: Opt[BazG[Baz]] + y: Opt[BazGRef[Baz]] + +var z = Baz() +assert z.x.x is string +assert z.y.x is int + +import options + +type + Person = ref object + parent: Option[Person] + +proc newPerson(parent: Option[Person]): Person = + Person(parent: parent) + +var person = newPerson(none(Person)) diff --git a/tests/range/timplicitrangedownsizing.nim b/tests/range/timplicitrangedownsizing.nim index 1b10f2f322..930d91bb11 100644 --- a/tests/range/timplicitrangedownsizing.nim +++ b/tests/range/timplicitrangedownsizing.nim @@ -70,4 +70,9 @@ var smallFloatRange: SmallFloat = SmallFloat(5.0) acceptWideFloat(smallFloatRange) # OK - SmallFloat (0.0..10.0) fits in WideFloatRange (0.0..100.0) var wf: WideFloatRange -wf = smallFloatRange # OK - SmallFloat range fits in WideFloatRange \ No newline at end of file +wf = smallFloatRange # OK - SmallFloat range fits in WideFloatRange + +proc foo(x: Natural) = + discard + +foo(12) \ No newline at end of file diff --git a/tests/range/timplicitrangedownsizing2.nim b/tests/range/timplicitrangedownsizing2.nim new file mode 100644 index 0000000000..679fad60be --- /dev/null +++ b/tests/range/timplicitrangedownsizing2.nim @@ -0,0 +1,11 @@ +discard """ + matrix: "--warning:systemRangeConversion --warningaserror:systemRangeConversion" + action: "reject" + errormsg: "implicit range conversion int literal(12) -> Natural" +""" + + +proc foo(x: Natural) = + discard + +foo(12) \ No newline at end of file