mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-28 17:04:41 +00:00
Raising exceptions halfway through a memory allocation is undefined
behavior since exceptions themselves require multiple allocations and
the allocator functions are not reentrant.
It is of course also expensive performance-wise to introduce lots of
exception-raising code everywhere since it breaks many optimisations and
bloats the code.
Finally, performing pointer arithmetic with signed integers is incorrect
for example on on a 32-bit systems that allows up to 3gb of address
space for applications (large address extensions) and unnecessary
elsewhere - broadly, stuff inside the memory allocator is generated by
the compiler or controlled by the standard library meaning that
applications should not be forced to pay this price.
If we wanted to check for overflow, the right way would be in the
initial allocation location where both the size and count of objects is
known.
The code is updated to use the same arithmetic operator style as for
refc with unchecked operations rather than disabling overflow checking
wholesale in the allocator module - there are reasons for both, but
going with the existing flow seems like an easier place to start.
(cherry picked from commit 8b9972c8b6)
276 lines
8.7 KiB
Nim
276 lines
8.7 KiB
Nim
#
|
|
#
|
|
# Nim's Runtime Library
|
|
# (c) Copyright 2019 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
#[
|
|
In this new runtime we simplify the object layouts a bit: The runtime type
|
|
information is only accessed for the objects that have it and it's always
|
|
at offset 0 then. The ``ref`` object header is independent from the
|
|
runtime type and only contains a reference count.
|
|
]#
|
|
|
|
{.push raises: [].}
|
|
|
|
when defined(gcOrc):
|
|
const
|
|
rcIncrement = 0b10000 # so that lowest 4 bits are not touched
|
|
rcMask = 0b1111
|
|
rcShift = 4 # shift by rcShift to get the reference counter
|
|
|
|
else:
|
|
const
|
|
rcIncrement = 0b1000 # so that lowest 3 bits are not touched
|
|
rcMask = 0b111
|
|
rcShift = 3 # shift by rcShift to get the reference counter
|
|
|
|
const
|
|
orcLeakDetector = defined(nimOrcLeakDetector)
|
|
|
|
type
|
|
RefHeader = object
|
|
rc: int # the object header is now a single RC field.
|
|
# we could remove it in non-debug builds for the 'owned ref'
|
|
# design but this seems unwise.
|
|
when defined(gcOrc):
|
|
rootIdx: int # thanks to this we can delete potential cycle roots
|
|
# in O(1) without doubly linked lists
|
|
when defined(nimArcDebug) or defined(nimArcIds):
|
|
refId: int
|
|
when defined(gcOrc) and orcLeakDetector:
|
|
filename: cstring
|
|
line: int
|
|
|
|
Cell = ptr RefHeader
|
|
|
|
template setFrameInfo(c: Cell) =
|
|
when orcLeakDetector:
|
|
if framePtr != nil and framePtr.prev != nil:
|
|
c.filename = framePtr.prev.filename
|
|
c.line = framePtr.prev.line
|
|
else:
|
|
c.filename = nil
|
|
c.line = 0
|
|
|
|
template head(p: pointer): Cell =
|
|
cast[Cell](cast[int](p) -% sizeof(RefHeader))
|
|
|
|
const
|
|
traceCollector = defined(traceArc)
|
|
|
|
when defined(nimArcDebug):
|
|
include cellsets
|
|
|
|
const traceId = 20 # 1037
|
|
|
|
var gRefId: int
|
|
var freedCells: CellSet
|
|
elif defined(nimArcIds):
|
|
var gRefId: int
|
|
|
|
const traceId = -1
|
|
|
|
when defined(gcAtomicArc) and hasThreadSupport:
|
|
template decrement(cell: Cell): untyped =
|
|
discard atomicDec(cell.rc, rcIncrement)
|
|
template increment(cell: Cell): untyped =
|
|
discard atomicInc(cell.rc, rcIncrement)
|
|
template count(x: Cell): untyped =
|
|
atomicLoadN(x.rc.addr, ATOMIC_ACQUIRE) shr rcShift
|
|
else:
|
|
template decrement(cell: Cell): untyped =
|
|
cell.rc = cell.rc -% rcIncrement
|
|
template increment(cell: Cell): untyped =
|
|
cell.rc = cell.rc +% rcIncrement
|
|
template count(x: Cell): untyped =
|
|
x.rc shr rcShift
|
|
|
|
when not defined(nimHasQuirky):
|
|
{.pragma: quirky.}
|
|
|
|
proc nimNewObj(size, alignment: int): pointer {.compilerRtl.} =
|
|
let hdrSize = align(sizeof(RefHeader), alignment)
|
|
let s = size +% hdrSize
|
|
when defined(nimscript):
|
|
discard
|
|
else:
|
|
result = alignedAlloc0(s, alignment) +! hdrSize
|
|
when defined(nimArcDebug) or defined(nimArcIds):
|
|
head(result).refId = gRefId
|
|
atomicInc gRefId
|
|
if head(result).refId == traceId:
|
|
writeStackTrace()
|
|
cfprintf(cstderr, "[nimNewObj] %p %ld\n", result, head(result).count)
|
|
when traceCollector:
|
|
cprintf("[Allocated] %p result: %p\n", result -! sizeof(RefHeader), result)
|
|
setFrameInfo head(result)
|
|
|
|
proc nimNewObjUninit(size, alignment: int): pointer {.compilerRtl.} =
|
|
# Same as 'newNewObj' but do not initialize the memory to zero.
|
|
# The codegen proved for us that this is not necessary.
|
|
let hdrSize = align(sizeof(RefHeader), alignment)
|
|
let s = size + hdrSize
|
|
when defined(nimscript):
|
|
discard
|
|
else:
|
|
result = cast[ptr RefHeader](alignedAlloc(s, alignment) +! hdrSize)
|
|
head(result).rc = 0
|
|
when defined(gcOrc):
|
|
head(result).rootIdx = 0
|
|
when defined(nimArcDebug):
|
|
head(result).refId = gRefId
|
|
atomicInc gRefId
|
|
if head(result).refId == traceId:
|
|
writeStackTrace()
|
|
cfprintf(cstderr, "[nimNewObjUninit] %p %ld\n", result, head(result).count)
|
|
|
|
when traceCollector:
|
|
cprintf("[Allocated] %p result: %p\n", result -! sizeof(RefHeader), result)
|
|
setFrameInfo head(result)
|
|
|
|
proc nimDecWeakRef(p: pointer) {.compilerRtl, inl.} =
|
|
decrement head(p)
|
|
|
|
proc isUniqueRef*[T](x: ref T): bool {.inline.} =
|
|
## Returns true if the object `x` points to is uniquely referenced. Such
|
|
## an object can potentially be passed over to a different thread safely,
|
|
## if great care is taken. This queries the internal reference count of
|
|
## the object which is subject to lots of optimizations! In other words
|
|
## the value of `isUniqueRef` can depend on the used compiler version and
|
|
## optimizer setting.
|
|
## Nevertheless it can be used as a very valuable debugging tool and can
|
|
## be used to specify the constraints of a threading related API
|
|
## via `assert isUniqueRef(x)`.
|
|
head(cast[pointer](x)).rc == 0
|
|
|
|
proc nimIncRef(p: pointer) {.compilerRtl, inl.} =
|
|
when defined(nimArcDebug):
|
|
if head(p).refId == traceId:
|
|
writeStackTrace()
|
|
cfprintf(cstderr, "[IncRef] %p %ld\n", p, head(p).count)
|
|
|
|
increment head(p)
|
|
when traceCollector:
|
|
cprintf("[INCREF] %p\n", head(p))
|
|
|
|
when not defined(gcOrc) or defined(nimThinout):
|
|
proc unsureAsgnRef(dest: ptr pointer, src: pointer) {.inline.} =
|
|
# This is only used by the old RTTI mechanism and we know
|
|
# that 'dest[]' is nil and needs no destruction. Which is really handy
|
|
# as we cannot destroy the object reliably if it's an object of unknown
|
|
# compile-time type.
|
|
dest[] = src
|
|
if src != nil: nimIncRef src
|
|
|
|
when not defined(nimscript) and defined(nimArcDebug):
|
|
proc deallocatedRefId*(p: pointer): int =
|
|
## Returns the ref's ID if the ref was already deallocated. This
|
|
## is a memory corruption check. Returns 0 if there is no error.
|
|
let c = head(p)
|
|
if freedCells.data != nil and freedCells.contains(c):
|
|
result = c.refId
|
|
else:
|
|
result = 0
|
|
|
|
proc nimRawDispose(p: pointer, alignment: int) {.compilerRtl.} =
|
|
when not defined(nimscript):
|
|
when traceCollector:
|
|
cprintf("[Freed] %p\n", p -! sizeof(RefHeader))
|
|
when defined(nimOwnedEnabled):
|
|
if head(p).rc >= rcIncrement:
|
|
cstderr.rawWrite "[FATAL] dangling references exist\n"
|
|
rawQuit 1
|
|
when defined(nimArcDebug):
|
|
# we do NOT really free the memory here in order to reliably detect use-after-frees
|
|
if freedCells.data == nil: init(freedCells)
|
|
freedCells.incl head(p)
|
|
else:
|
|
let hdrSize = align(sizeof(RefHeader), alignment)
|
|
alignedDealloc(p -! hdrSize, alignment)
|
|
|
|
template `=dispose`*[T](x: owned(ref T)) = nimRawDispose(cast[pointer](x), T.alignOf)
|
|
#proc dispose*(x: pointer) = nimRawDispose(x)
|
|
|
|
proc nimDestroyAndDispose(p: pointer) {.compilerRtl, quirky, raises: [].} =
|
|
let rti = cast[ptr PNimTypeV2](p)
|
|
if rti.destructor != nil:
|
|
cast[DestructorProc](rti.destructor)(p)
|
|
when false:
|
|
cstderr.rawWrite cast[ptr PNimTypeV2](p)[].name
|
|
cstderr.rawWrite "\n"
|
|
if d == nil:
|
|
cstderr.rawWrite "bah, nil\n"
|
|
else:
|
|
cstderr.rawWrite "has destructor!\n"
|
|
nimRawDispose(p, rti.align)
|
|
|
|
when defined(gcOrc):
|
|
when defined(nimThinout):
|
|
include cyclebreaker
|
|
else:
|
|
include orc
|
|
#include cyclecollector
|
|
|
|
proc nimDecRefIsLast(p: pointer): bool {.compilerRtl, inl.} =
|
|
result = false
|
|
if p != nil:
|
|
var cell = head(p)
|
|
|
|
when defined(nimArcDebug):
|
|
if cell.refId == traceId:
|
|
writeStackTrace()
|
|
cfprintf(cstderr, "[DecRef] %p %ld\n", p, cell.count)
|
|
|
|
when defined(gcAtomicArc) and hasThreadSupport:
|
|
# `atomicDec` returns the new value
|
|
if atomicDec(cell.rc, rcIncrement) == -rcIncrement:
|
|
result = true
|
|
when traceCollector:
|
|
cprintf("[ABOUT TO DESTROY] %p\n", cell)
|
|
else:
|
|
if cell.count == 0:
|
|
result = true
|
|
when traceCollector:
|
|
cprintf("[ABOUT TO DESTROY] %p\n", cell)
|
|
else:
|
|
decrement cell
|
|
# According to Lins it's correct to do nothing else here.
|
|
when traceCollector:
|
|
cprintf("[DECREF] %p\n", cell)
|
|
|
|
proc GC_unref*[T](x: ref T) =
|
|
## New runtime only supports this operation for 'ref T'.
|
|
var y {.cursor.} = x
|
|
`=destroy`(y)
|
|
|
|
proc GC_ref*[T](x: ref T) =
|
|
## New runtime only supports this operation for 'ref T'.
|
|
if x != nil: nimIncRef(cast[pointer](x))
|
|
|
|
when not defined(gcOrc):
|
|
template GC_fullCollect* =
|
|
## Forces a full garbage collection pass. With `--mm:arc` a nop.
|
|
discard
|
|
|
|
template setupForeignThreadGc* =
|
|
## With `--mm:arc` a nop.
|
|
discard
|
|
|
|
template tearDownForeignThreadGc* =
|
|
## With `--mm:arc` a nop.
|
|
discard
|
|
|
|
proc isObjDisplayCheck(source: PNimTypeV2, targetDepth: int16, token: uint32): bool {.compilerRtl, inl.} =
|
|
result = targetDepth <= source.depth and source.display[targetDepth] == token
|
|
|
|
when defined(gcDestructors):
|
|
proc nimGetVTable(p: pointer, index: int): pointer
|
|
{.compilerRtl, inline, raises: [].} =
|
|
result = cast[ptr PNimTypeV2](p).vTable[index]
|
|
|
|
{.pop.} # raises: []
|