Skip to content

Commit

Permalink
Merge branch 'devel' into pr_gcc14_action
Browse files Browse the repository at this point in the history
  • Loading branch information
ringabout authored Jun 6, 2024
2 parents 8e0978d + 69d0b73 commit 71c2172
Show file tree
Hide file tree
Showing 13 changed files with 396 additions and 109 deletions.
61 changes: 1 addition & 60 deletions compiler/sem.nim
Original file line number Diff line number Diff line change
Expand Up @@ -222,66 +222,7 @@ proc shouldCheckCaseCovered(caseTyp: PType): bool =
else:
discard

proc endsInNoReturn(n: PNode): bool =
## check if expr ends the block like raising or call of noreturn procs do
result = false # assume it does return

template checkBranch(branch) =
if not endsInNoReturn(branch):
# proved a branch returns
return false

var it = n
# skip these beforehand, no special handling needed
while it.kind in {nkStmtList, nkStmtListExpr} and it.len > 0:
it = it.lastSon

case it.kind
of nkIfStmt:
var hasElse = false
for branch in it:
checkBranch:
if branch.len == 2:
branch[1]
elif branch.len == 1:
hasElse = true
branch[0]
else:
raiseAssert "Malformed `if` statement during endsInNoReturn"
# none of the branches returned
result = hasElse # Only truly a no-return when it's exhaustive
of nkCaseStmt:
let caseTyp = skipTypes(it[0].typ, abstractVar-{tyTypeDesc})
# semCase should already have checked for exhaustiveness in this case
# effectively the same as having an else
var hasElse = caseTyp.shouldCheckCaseCovered()

# actual noreturn checks
for i in 1 ..< it.len:
let branch = it[i]
checkBranch:
case branch.kind
of nkOfBranch:
branch[^1]
of nkElifBranch:
branch[1]
of nkElse:
hasElse = true
branch[0]
else:
raiseAssert "Malformed `case` statement in endsInNoReturn"
# Can only guarantee a noreturn if there is an else or it's exhaustive
result = hasElse
of nkTryStmt:
checkBranch(it[0])
for i in 1 ..< it.len:
let branch = it[i]
checkBranch(branch[^1])
# none of the branches returned
result = true
else:
result = it.kind in nkLastBlockStmts or
it.kind in nkCallKinds and it[0].kind == nkSym and sfNoReturn in it[0].sym.flags
proc endsInNoReturn(n: PNode): bool

proc commonType*(c: PContext; x: PType, y: PNode): PType =
# ignore exception raising branches in case/if expressions
Expand Down
3 changes: 2 additions & 1 deletion compiler/semfold.nim
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,8 @@ proc getConstExpr(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode
of nkCast:
var a = getConstExpr(m, n[1], idgen, g)
if a == nil: return
if n.typ != nil and n.typ.kind in NilableTypes:
if n.typ != nil and n.typ.kind in NilableTypes and
not (n.typ.kind == tyProc and a.typ.kind == tyProc):
# we allow compile-time 'cast' for pointer types:
result = a
result.typ = n.typ
Expand Down
145 changes: 132 additions & 13 deletions compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,140 @@ proc semExprBranchScope(c: PContext, n: PNode; expectedType: PType = nil): PNode
closeScope(c)

const
skipForDiscardable = {nkIfStmt, nkIfExpr, nkCaseStmt, nkOfBranch,
nkElse, nkStmtListExpr, nkTryStmt, nkFinally, nkExceptBranch,
skipForDiscardable = {nkStmtList, nkStmtListExpr,
nkOfBranch, nkElse, nkFinally, nkExceptBranch,
nkElifBranch, nkElifExpr, nkElseExpr, nkBlockStmt, nkBlockExpr,
nkHiddenStdConv, nkHiddenDeref}

proc implicitlyDiscardable(n: PNode): bool =
var n = n
while n.kind in skipForDiscardable: n = n.lastSon
result = n.kind in nkLastBlockStmts or
(isCallExpr(n) and n[0].kind == nkSym and
sfDiscardable in n[0].sym.flags)
# same traversal as endsInNoReturn
template checkBranch(branch) =
if not implicitlyDiscardable(branch):
return false

var it = n
# skip these beforehand, no special handling needed
while it.kind in skipForDiscardable and it.len > 0:
it = it.lastSon

case it.kind
of nkIfExpr, nkIfStmt:
for branch in it:
checkBranch:
if branch.len == 2:
branch[1]
elif branch.len == 1:
branch[0]
else:
raiseAssert "Malformed `if` statement during implicitlyDiscardable"
# all branches are discardable
result = true
of nkCaseStmt:
for i in 1 ..< it.len:
let branch = it[i]
checkBranch:
case branch.kind
of nkOfBranch:
branch[^1]
of nkElifBranch:
branch[1]
of nkElse:
branch[0]
else:
raiseAssert "Malformed `case` statement in endsInNoReturn"
# all branches are discardable
result = true
of nkTryStmt:
checkBranch(it[0])
for i in 1 ..< it.len:
let branch = it[i]
if branch.kind != nkFinally:
checkBranch(branch[^1])
# all branches are discardable
result = true
of nkCallKinds:
result = it[0].kind == nkSym and {sfDiscardable, sfNoReturn} * it[0].sym.flags != {}
of nkLastBlockStmts:
result = true
else:
result = false

proc endsInNoReturn(n: PNode, returningNode: var PNode): bool =
## check if expr ends the block like raising or call of noreturn procs do
result = false # assume it does return

template checkBranch(branch) =
if not endsInNoReturn(branch, returningNode):
# proved a branch returns
return false

var it = n
# skip these beforehand, no special handling needed
while it.kind in skipForDiscardable and it.len > 0:
it = it.lastSon

case it.kind
of nkIfExpr, nkIfStmt:
var hasElse = false
for branch in it:
checkBranch:
if branch.len == 2:
branch[1]
elif branch.len == 1:
hasElse = true
branch[0]
else:
raiseAssert "Malformed `if` statement during endsInNoReturn"
# none of the branches returned
result = hasElse # Only truly a no-return when it's exhaustive
of nkCaseStmt:
let caseTyp = skipTypes(it[0].typ, abstractVar-{tyTypeDesc})
# semCase should already have checked for exhaustiveness in this case
# effectively the same as having an else
var hasElse = caseTyp.shouldCheckCaseCovered()

# actual noreturn checks
for i in 1 ..< it.len:
let branch = it[i]
checkBranch:
case branch.kind
of nkOfBranch:
branch[^1]
of nkElifBranch:
branch[1]
of nkElse:
hasElse = true
branch[0]
else:
raiseAssert "Malformed `case` statement in endsInNoReturn"
# Can only guarantee a noreturn if there is an else or it's exhaustive
result = hasElse
of nkTryStmt:
checkBranch(it[0])
var lastIndex = it.len - 1
if it[lastIndex].kind == nkFinally:
# if finally is noreturn, then the entire statement is noreturn
if endsInNoReturn(it[lastIndex][^1], returningNode):
return true
dec lastIndex
for i in 1 .. lastIndex:
let branch = it[i]
checkBranch(branch[^1])
# none of the branches returned
result = true
of nkLastBlockStmts:
result = true
of nkCallKinds:
result = it[0].kind == nkSym and sfNoReturn in it[0].sym.flags
if not result:
returningNode = it
else:
result = false
returningNode = it

proc endsInNoReturn(n: PNode): bool =
var dummy: PNode = nil
result = endsInNoReturn(n, dummy)

proc fixNilType(c: PContext; n: PNode) =
if isAtom(n):
Expand All @@ -165,13 +288,9 @@ proc discardCheck(c: PContext, result: PNode, flags: TExprFlags) =
localError(c.config, result.info, "expression has no type: " &
renderTree(result, {renderNoComments}))
else:
var n = result
while n.kind in skipForDiscardable:
if n.kind == nkTryStmt: n = n[0]
else: n = n.lastSon

# Ignore noreturn procs since they don't have a type
if n.endsInNoReturn:
var n = result
if result.endsInNoReturn(n):
return

var s = "expression '" & $n & "' is of type '" &
Expand Down
12 changes: 6 additions & 6 deletions lib/pure/options.nim
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,10 @@ proc option*[T](val: sink T): Option[T] {.inline.} =
assert option[Foo](nil).isNone
assert option(42).isSome

result.val = val
when T isnot SomePointer:
result.has = true
when T is SomePointer:
result = Option[T](val: val)
else:
result = Option[T](has: true, val: val)

proc some*[T](val: sink T): Option[T] {.inline.} =
## Returns an `Option` that has the value `val`.
Expand All @@ -136,10 +137,9 @@ proc some*[T](val: sink T): Option[T] {.inline.} =

when T is SomePointer:
assert not val.isNil
result.val = val
result = Option[T](val: val)
else:
result.has = true
result.val = val
result = Option[T](has: true, val: val)

proc none*(T: typedesc): Option[T] {.inline.} =
## Returns an `Option` for this type that has no value.
Expand Down
2 changes: 1 addition & 1 deletion lib/std/syncio.nim
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,7 @@ proc writeFile*(filename: string, content: openArray[byte]) {.since: (1, 1).} =
var f: File = nil
if open(f, filename, fmWrite):
try:
f.writeBuffer(unsafeAddr content[0], content.len)
discard f.writeBuffer(unsafeAddr content[0], content.len)
finally:
close(f)
else:
Expand Down
51 changes: 24 additions & 27 deletions lib/system/alloc.nim
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ type
freeList: ptr FreeCell
free: int # how many bytes remain
acc: int # accumulator for small object allocation
when defined(gcDestructors):
sharedFreeList: ptr FreeCell # make no attempt at avoiding false sharing for now for this object field
data {.align: MemAlign.}: UncheckedArray[byte] # start of usable memory

BigChunk = object of BaseChunk # not necessarily > PageSize!
Expand All @@ -109,7 +107,9 @@ type
MemRegion = object
when not defined(gcDestructors):
minLargeObj, maxLargeObj: int
freeSmallChunks: array[0..max(1,SmallChunkSize div MemAlign-1), PSmallChunk]
freeSmallChunks: array[0..max(1, SmallChunkSize div MemAlign-1), PSmallChunk]
when defined(gcDestructors):
sharedFreeLists: array[0..max(1, SmallChunkSize div MemAlign-1), ptr FreeCell]
flBitmap: uint32
slBitmap: array[RealFli, uint32]
matrix: array[RealFli, array[MaxSli, PBigChunk]]
Expand Down Expand Up @@ -777,8 +777,10 @@ when defined(gcDestructors):
sysAssert c.next == nil, "c.next pointer must be nil"
atomicPrepend a.sharedFreeListBigChunks, c

proc addToSharedFreeList(c: PSmallChunk; f: ptr FreeCell) {.inline.} =
atomicPrepend c.sharedFreeList, f
proc addToSharedFreeList(c: PSmallChunk; f: ptr FreeCell; size: int) {.inline.} =
atomicPrepend c.owner.sharedFreeLists[size], f

const MaxSteps = 20

proc compensateCounters(a: var MemRegion; c: PSmallChunk; size: int) =
# rawDealloc did NOT do the usual:
Expand All @@ -788,30 +790,26 @@ when defined(gcDestructors):
# we split the list in order to achieve bounded response times.
var it = c.freeList
var x = 0
var maxIters = 20 # make it time-bounded
while it != nil:
if maxIters == 0:
let rest = it.next.loada
if rest != nil:
it.next.storea nil
addToSharedFreeList(c, rest)
break
inc x, size
it = it.next.loada
dec maxIters
inc(c.free, x)
let chunk = cast[PSmallChunk](pageAddr(it))
inc(chunk.free, x)
it = it.next
dec(a.occ, x)

proc freeDeferredObjects(a: var MemRegion; root: PBigChunk) =
var it = root
var maxIters = 20 # make it time-bounded
var maxIters = MaxSteps # make it time-bounded
while true:
let rest = it.next.loada
it.next.storea nil
deallocBigChunk(a, cast[PBigChunk](it))
if maxIters == 0:
let rest = it.next.loada
it.next.storea nil
addToSharedFreeListBigChunks(a, rest)
if rest != nil:
addToSharedFreeListBigChunks(a, rest)
sysAssert a.sharedFreeListBigChunks != nil, "re-enqueing failed"
break
it = it.next.loada
it = rest
dec maxIters
if it == nil: break

Expand All @@ -835,8 +833,6 @@ proc rawAlloc(a: var MemRegion, requestedSize: int): pointer =
sysAssert c.size == PageSize, "rawAlloc 3"
c.size = size
c.acc = size
when defined(gcDestructors):
c.sharedFreeList = nil
c.free = SmallChunkSize - smallChunkOverhead() - size
sysAssert c.owner == addr(a), "rawAlloc: No owner set!"
c.next = nil
Expand All @@ -853,10 +849,11 @@ proc rawAlloc(a: var MemRegion, requestedSize: int): pointer =
when defined(gcDestructors):
if c.freeList == nil:
when hasThreadSupport:
c.freeList = atomicExchangeN(addr c.sharedFreeList, nil, ATOMIC_RELAXED)
# Steal the entire list from `sharedFreeList`:
c.freeList = atomicExchangeN(addr a.sharedFreeLists[s], nil, ATOMIC_RELAXED)
else:
c.freeList = c.sharedFreeList
c.sharedFreeList = nil
c.freeList = a.sharedFreeLists[s]
a.sharedFreeLists[s] = nil
compensateCounters(a, c, size)
if c.freeList == nil:
sysAssert(c.acc + smallChunkOverhead() + size <= SmallChunkSize,
Expand Down Expand Up @@ -923,7 +920,7 @@ proc rawDealloc(a: var MemRegion, p: pointer) =
if isSmallChunk(c):
# `p` is within a small chunk:
var c = cast[PSmallChunk](c)
var s = c.size
let s = c.size
# ^ We might access thread foreign storage here.
# The other thread cannot possibly free this block as it's still alive.
var f = cast[ptr FreeCell](p)
Expand Down Expand Up @@ -957,7 +954,7 @@ proc rawDealloc(a: var MemRegion, p: pointer) =
freeBigChunk(a, cast[PBigChunk](c))
else:
when defined(gcDestructors):
addToSharedFreeList(c, f)
addToSharedFreeList(c, f, s div MemAlign)
sysAssert(((cast[int](p) and PageMask) - smallChunkOverhead()) %%
s == 0, "rawDealloc 2")
else:
Expand Down
Loading

0 comments on commit 71c2172

Please sign in to comment.