The memory of the VM. All VM values live in memory that is owned by the VM and that is tracked as a set of regions. Every access through a pointer that the VM cannot prove to be valid is checked against the regions so that a bogus cast produces a VM error instead of crashing the compiler.
There are different kinds of regions:
- the VM stack: frames are bump allocated from stack segments.
- the globals segment: compile-time globals; never freed.
- the const segment: constant data (string literals, const values); read-only and never freed.
- heap chunks: blocks for new, string and seq payloads, alloc.
- big blocks: heap blocks too large for a chunk get a region of their own.
- foreign regions: memory handed to the VM by FFI calls.
Regions never move, so addresses are stable and the VM can use real host pointers for ptr, ref, var etc.
NimNode and typedesc values are represented by 32 bit handles: indexes into tables of this module. Handle 0 always means nil. NimNode handles are reference counted: the VM code got destructors for them injected (see liftdestructors), a handle whose count drops to zero is freed and its slot is reused. A handle that is part of the VM's code (a NimNode literal) is pinned: it is never freed. Procs are represented by addresses within proc regions: these addresses are unique, so they can be told apart from integers that are cast to a proc type, yet they cannot be read or written.
Types
Address = uint64
- a raw VM address; the VM uses host pointers, but stores them in 8 bytes even on a 32 bit host, see VmPtrSize Source Edit
Region = object start*, size*: Address kind*: RegionKind
- Source Edit
RegionKind = enum rgStack, rgGlobals, rgConst, rgHeapChunk, rgBigBlock, rgForeign, rgProcs
- Source Edit
Consts
BlockAlign = 16
- Source Edit
BlockHeaderSize = 16
- Source Edit
RcImmortal = 4611686018427387904'i64
- rc flag of a ref cell in constant memory: never counted, never freed. Source Edit
RefHeaderSize = 8
- the refcount precedes the object directly Source Edit
StackSegmentSize = 2097152
- Source Edit
StrlitFlag = 4611686018427387904'i64
- cap flag of a string or seq payload that lives in constant memory and must not be freed or mutated. Same value as the 64 bit runtime's NIM_STRLIT_FLAG. Source Edit
Procs
proc allocConst(m: var VmMemory; size, align: int): Address {....raises: [], tags: [], forbids: [].}
- storage for constant data; read-only for VM code. Source Edit
proc allocGlobal(m: var VmMemory; size, align: int): Address {....raises: [], tags: [], forbids: [].}
- storage for a compile-time global; zeroed, never freed, never moved. Source Edit
proc decNodeRef(m: var VmMemory; h: int64) {....raises: [], tags: [], forbids: [].}
- Source Edit
proc decRefIsLast(p: Address): bool {.inline, ...raises: [], tags: [], forbids: [].}
- nimDecRefIsLast: true if this was the last reference; the object then has to be destroyed and disposed. Source Edit
proc disposeRef(m: var VmMemory; p: Address; align: int): bool {....raises: [], tags: [], forbids: [].}
- nimRawDispose: frees the memory of a ref. Returns false for an invalid pointer. Source Edit
proc findRegion(m: var VmMemory; a: Address): int {....raises: [], tags: [], forbids: [].}
- index of the region that contains a or -1. Source Edit
proc heapDealloc(m: var VmMemory; p: Address): bool {....raises: [], tags: [], forbids: [].}
- frees a heap block. Returns false if p is not a live heap block (double free, interior pointer, not VM heap memory). Source Edit
proc incNodeRef(m: var VmMemory; h: int64) {....raises: [], tags: [], forbids: [].}
- Source Edit
proc isConstMemory(m: var VmMemory; a: Address): bool {....raises: [], tags: [], forbids: [].}
- Source Edit
proc isFreedBlock(m: var VmMemory; p: Address): bool {....raises: [], tags: [], forbids: [].}
- true if p is the start of a heap block that was freed. Source Edit
proc isHeapBlock(m: var VmMemory; p: Address): bool {....raises: [], tags: [], forbids: [].}
- true if p is the start of a live heap block. Source Edit
proc isLiteralPayload(p: Address): bool {.inline, ...raises: [], tags: [], forbids: [].}
- Source Edit
proc loadString(src: Address): string {....raises: [], tags: [], forbids: [].}
- reads the string value at src. Assumes src was checked. Source Edit
proc newConstPayload(m: var VmMemory; cap, elemSize, elemAlign: int; isString: bool): Address {....raises: [], tags: [], forbids: [].}
- allocates a payload in constant memory; it is flagged with StrlitFlag. Source Edit
proc newConstRef(m: var VmMemory; size, align: int): Address {....raises: [], tags: [], forbids: [].}
- Source Edit
proc newPayload(m: var VmMemory; cap, elemSize, elemAlign: int; isString: bool): Address {. ...raises: [], tags: [], forbids: [].}
- allocates a zeroed payload for cap elements on the heap. Source Edit
proc nodeHandle(m: var VmMemory; n: PNode): int32 {....raises: [], tags: [], forbids: [].}
- a new reference to n: the caller owns it and has to release it with decNodeRef, which the VM code's injected destructors do. Source Edit
proc payloadCap(p: Address): int {.inline, ...raises: [], tags: [], forbids: [].}
- Source Edit
proc payloadDataOffset(elemAlign: int): int {.inline, ...raises: [], tags: [], forbids: [].}
- Source Edit
proc pinnedNodeHandle(m: var VmMemory; n: PNode): int32 {....raises: [], tags: [], forbids: [].}
- a handle for n that is never freed: for NimNode literals in the VM's code. Source Edit
proc refBlockOffset(align: int): int {.inline, ...raises: [], tags: [], forbids: [].}
- Source Edit
proc registerForeign(m: var VmMemory; p: pointer; size: int) {....raises: [], tags: [], forbids: [].}
- makes memory that the VM did not allocate (FFI) accessible. Source Edit
proc storeString(m: var VmMemory; dest: Address; s: string; inConst: bool) {. ...raises: [], tags: [], forbids: [].}
- writes a string value (len, p) to dest. The empty string has no payload. Source Edit
proc typeHandle(m: var VmMemory; t: PType): int32 {....raises: [], tags: [], forbids: [].}
- Source Edit
proc unregisterForeign(m: var VmMemory; p: pointer) {....raises: [], tags: [], forbids: [].}
- Source Edit