mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-03 04:13:40 +00:00
`testament --ic` on `tests/destructor` went from 10 failures to 3; the three
left are all `--newruntime`, whose owned-ref RTTI destructor is still missing
(the object type's `=destroy` never reaches the `cg` that emits the type info).
* **global destructors are never run.** `graph.globalDestructors` is filled
while a module's top level goes through `injectDestructorCalls`, and
whole-program cgen empties the list into the main module's init proc — which
IS the program body, so the calls land at exit. Under `nim ic` every module's
`cg` is a separate process and main's only ever saw its own entries: a
module-level `var` with a `=destroy` in an imported module was simply never
destroyed. Each module now wraps its own list in a nullary exported proc and
announces the name in its `.c.nif` meta head (`CnifVersion` 4 -> 5); main's
`cg` reads the heads it already reads for init/datInit and calls them in
reverse dependency order. New test `tests/ic/tglobal_dtors.nim` pins the
order against the oracle.
* **`spawn` died with "system module needs: nimArgsPassingDone".** A module
loaded from a NIF is named by its mangled suffix, so `loadCompilerProc`'s
`module.name.s == "threadpool"` could never match. The backend loads the whole
program before codegen, so consult every loaded module's index instead; misses
are final and remembered (`getCompilerProc` doubles as a presence probe).
* **`new(x, finalizer)` died with "environment misses: x".** `semmagic`'s
finalizer-to-destructor wrapper copies the original's AST but only rewrites
the parameter, leaving `ast[namePos]` naming the ORIGINAL proc. ast2nif
re-derives a routine's serialized AST from `ast[namePos].sym.ast`, so the
wrapper serialized the original's body — whose parameter belongs to the
original — and lambda lifting saw it as a captured variable of another proc.
The copy now names itself, the invariant every other routine AST keeps.
* **a top-level `let (a, b) = f()` copied instead of moved.** A module's own
symbol is the owner of every top-level symbol and is written as a real `(sd)`,
so the loader minted a SECOND `skModule` PSym for it — and `sym.owner == owner`
is an identity test in `aliasanalysis.isAnalysableFieldAccess`, which made
every module-level location un-analysable. Hard error for a type with a
disabled `=copy`. Bind the NIF name to the one registered module symbol.
Backend only: doing it under `nim m` costs `times.toDateTimeByWeek` its
inferred `raises`.
* **a doubly linked list leaked its whole contents.** A field USE serializes as
a bare `SymUse` with nowhere to put symbol flags, so `trees.isCursor` said
"not a cursor" for every loaded field and `DoublyLinkedNode.prev` became a
COUNTED reference: every node held its predecessor alive and no refcount ever
hit zero. `{.cursor.}` now rides in the NIF name marker (`` `fc `` next to
`` `f ``), which def and use derive from the same `PSym`.
* **`--expandArc` came out shuffled.** `moduleSymbolStubs` iterated a `Table`,
i.e. hash order, so the `lower` stage transformed a module's routines in an
arbitrary order — not even stable between two compilers. Order by index
offset, which is source order.
Also: `testament`'s `generatedFile` did not include the matrix entry in the
nimcache key its caller uses, so every `ccodeCheck` test with a `matrix:`
reported `reCodeNotFound`.
`koch bootic` still reaches its fixed point; `tests/ic` and the classic
`tests/destructor` are unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
247 lines
12 KiB
Nim
247 lines
12 KiB
Nim
#
|
|
#
|
|
# The Nim Compiler
|
|
# (c) Copyright 2012 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
## This module contains the data structures for the C code generation phase.
|
|
|
|
import
|
|
ast, ropes, options,
|
|
lineinfos, pathutils, modulegraphs, cbuilderbase
|
|
|
|
import std/[intsets, tables, sets]
|
|
|
|
type
|
|
TLabel* = Rope # for the C generator a label is just a rope
|
|
TCFileSection* = enum # the sections a generated C file consists of
|
|
cfsHeaders, # section for C include file headers
|
|
cfsFrameDefines # section for nim frame macros
|
|
cfsForwardTypes, # section for C forward typedefs
|
|
cfsTypes, # section for C typedefs
|
|
cfsSeqTypes, # section for sequence types only
|
|
# this is needed for strange type generation
|
|
# reasons
|
|
cfsTypeInfo, # section for type information (ag ABI checks)
|
|
cfsProcHeaders, # section for C procs prototypes
|
|
cfsStrData, # section for constant string literals
|
|
cfsData, # section for C constant data
|
|
cfsVars, # section for C variable declarations
|
|
cfsProcs, # section for C procs that are not inline
|
|
cfsInitProc, # section for the C init proc
|
|
cfsDatInitProc, # section for the C datInit proc
|
|
cfsTypeInit1, # section 1 for declarations of type information
|
|
cfsTypeInit3, # section 3 for init of type information
|
|
cfsDynLibInit, # section for init of dynamic library binding
|
|
TCTypeKind* = enum # describes the type kind of a C type
|
|
ctVoid, ctChar, ctBool,
|
|
ctInt, ctInt8, ctInt16, ctInt32, ctInt64,
|
|
ctFloat, ctFloat32, ctFloat64, ctFloat128,
|
|
ctUInt, ctUInt8, ctUInt16, ctUInt32, ctUInt64,
|
|
ctArray, ctPtrToArray, ctStruct, ctPtr, ctNimStr, ctNimSeq, ctProc,
|
|
ctCString
|
|
TCFileSections* = array[TCFileSection, Builder] # represents a generated C file
|
|
TCProcSection* = enum # the sections a generated C proc consists of
|
|
cpsLocals, # section of local variables for C proc
|
|
cpsInit, # section for init of variables for C proc
|
|
cpsStmts # section of local statements for C proc
|
|
TCProcSections* = array[TCProcSection, Builder] # represents a generated C proc
|
|
BModule* = ref TCGen
|
|
BProc* = ref TCProc
|
|
TBlock* = object
|
|
id*: int # the ID of the label; positive means that it
|
|
label*: Rope # generated text for the label
|
|
# nil if label is not used
|
|
sections*: TCProcSections # the code belonging
|
|
isLoop*: bool # whether block is a loop
|
|
nestedTryStmts*: int16 # how many try statements is it nested into
|
|
nestedExceptStmts*: int16 # how many except statements is it nested into
|
|
frameLen*: int16
|
|
|
|
TCProcFlag* = enum
|
|
beforeRetNeeded,
|
|
threadVarAccessed,
|
|
hasCurFramePointer,
|
|
noSafePoints,
|
|
nimErrorFlagAccessed,
|
|
nimErrorFlagDeclared,
|
|
nimErrorFlagDisabled
|
|
|
|
TCProc = object # represents C proc that is currently generated
|
|
prc*: PSym # the Nim proc that this C proc belongs to
|
|
flags*: set[TCProcFlag]
|
|
lastLineInfo*: TLineInfo # to avoid generating excessive 'nimln' statements
|
|
currLineInfo*: TLineInfo # AST codegen will make this superfluous
|
|
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, isHidden: bool, label: Natural]]
|
|
# in how many nested try statements we are
|
|
# (the vars must be volatile then)
|
|
# `inExcept` is true when we are in the except part of a try block.
|
|
# `isHidden` is true for compiler-injected `nkHiddenTryStmt` wrappers
|
|
# (e.g. ARC's destructor try/finally around `except T as e:` bodies);
|
|
# finallyActions walks past such wrappers to reach the user's try.
|
|
finallySafePoints*: seq[Rope] # For correctly cleaning up exceptions when
|
|
# using return in finally statements
|
|
labels*: Natural # for generating unique labels in the C proc
|
|
blocks*: seq[TBlock] # nested blocks
|
|
breakIdx*: int # the block that will be exited
|
|
# with a regular break
|
|
options*: TOptions # options that should be used for code
|
|
# generation; this is the same as prc.options
|
|
# unless prc == nil
|
|
optionsStack*: seq[(TOptions, TNoteKinds)]
|
|
module*: BModule # used to prevent excessive parameter passing
|
|
withinLoop*: int # > 0 if we are within a loop
|
|
splitDecls*: int # > 0 if we are in some context for C++ that
|
|
# requires 'T x = T()' to become 'T x; x = T()'
|
|
# (yes, C++ is weird like that)
|
|
withinTryWithExcept*: int # required for goto based exception handling
|
|
withinBlockLeaveActions*: int # complex to explain
|
|
sigConflicts*: CountTable[string]
|
|
inUncheckedAssignSection*: int
|
|
|
|
TTypeSeq* = seq[PType]
|
|
TypeCache* = Table[SigHash, Rope]
|
|
TypeCacheWithOwner* = Table[SigHash, tuple[str: Rope, owner: int32]]
|
|
|
|
CodegenFlag* = enum
|
|
preventStackTrace, # true if stack traces need to be prevented
|
|
usesThreadVars, # true if the module uses a thread var
|
|
frameDeclared, # hack for ROD support so that we don't declare
|
|
# a frame var twice in an init proc
|
|
isHeaderFile, # C source file is the header file
|
|
includesStringh, # C source file already includes ``<string.h>``
|
|
objHasKidsValid # whether we can rely on tfObjHasKids
|
|
useAliveDataFromDce # use the `alive: IntSet` field instead of
|
|
# computing alive data on our own.
|
|
|
|
BModuleList* = ref object of RootObj
|
|
mainModProcs*, mainModInit*, otherModsInit*, mainDatInit*: Builder
|
|
mapping*: Rope # the generated mapping file (if requested)
|
|
mods*: seq[BModule] # list of all compiled modules
|
|
modulesClosed*: seq[BModule] # list of the same compiled modules, but in the order they were closed
|
|
forwardedProcs*: seq[PSym] # procs that did not yet have a body
|
|
generatedHeader*: BModule
|
|
typeInfoMarker*: TypeCacheWithOwner
|
|
typeInfoMarkerV2*: TypeCacheWithOwner
|
|
config*: ConfigRef
|
|
graph*: ModuleGraph
|
|
strVersion*, seqVersion*: int # version of the string/seq implementation to use
|
|
|
|
nimtv*: Builder # Nim thread vars; the struct body
|
|
nimtvDeps*: seq[PType] # type deps: every module needs whole struct
|
|
nimtvDeclared*: IntSet # so that every var/field exists only once
|
|
# in the struct
|
|
# 'nimtv' is incredibly hard to modularize! Best
|
|
# effort is to store all thread vars in a ROD
|
|
# section and with their type deps and load them
|
|
# unconditionally...
|
|
# nimtvDeps is VERY hard to cache because it's
|
|
# not a list of IDs nor can it be made to be one.
|
|
mangledPrcs*: HashSet[string]
|
|
|
|
TCGen = object of PPassContext # represents a C source file
|
|
s*: TCFileSections # sections of the C file
|
|
flags*: set[CodegenFlag]
|
|
module*: PSym
|
|
filename*: AbsoluteFile
|
|
cfilename*: AbsoluteFile # filename of the module (including path,
|
|
# without extension)
|
|
tmpBase*: Rope # base for temp identifier generation
|
|
typeCache*: TypeCache # cache the generated types
|
|
typeABICache*: HashSet[SigHash] # cache for ABI checks; reusing typeCache
|
|
# would be ideal but for some reason enums
|
|
# don't seem to get cached so it'd generate
|
|
# 1 ABI check per occurrence in code
|
|
forwTypeCache*: TypeCache # cache for forward declarations of types
|
|
declaredThings*: IntSet # things we have declared in this .c file
|
|
declaredProtos*: IntSet # prototypes we have declared in this .c file
|
|
emittedContentDefs*: HashSet[string]
|
|
# cmdNifC per-module backend: content-addressed C names (generic
|
|
# instances and synthesized hooks) whose body this TU already emitted.
|
|
# Distinct symbols (minted in different source modules) can share one
|
|
# `_i<disamb>` name; `declaredThings` keys on symbol id and lets the
|
|
# second one through, so we dedup the body by name here instead.
|
|
queue*: seq[PSym] # queue of procs to generate
|
|
alive*: IntSet # symbol IDs of alive data as computed by `dce.nim`
|
|
headerFiles*: seq[string] # needed headers to include
|
|
typeInfoMarker*: TypeCache # needed for generating type information
|
|
typeInfoMarkerV2*: TypeCache
|
|
initProc*: BProc # code for init procedure
|
|
preInitProc*: BProc # code executed before the init proc
|
|
hcrCreateTypeInfosProc*: Builder # type info globals are in here when HCR=on
|
|
inHcrInitGuard*: bool # We are currently within a HCR reloading guard.
|
|
hcrInitGuard*: IfBuilder
|
|
typeStack*: TTypeSeq # used for type generation
|
|
dataCache*: TNodeTable
|
|
typeNodes*, nimTypes*: int # used for type info generation
|
|
typeNodesName*, nimTypesName*: Rope # used for type info generation
|
|
labels*: Natural # for generating unique module-scope names
|
|
extensionLoaders*: array['0'..'9', Builder] # special procs for the
|
|
# OpenGL wrapper
|
|
sigConflicts*: CountTable[SigHash]
|
|
icImplMods*: IntSet # module ids whose routine BODIES this TU
|
|
# embeds (redirected defs, shared instances,
|
|
# hooks); recorded as the artifact's cdeps so
|
|
# the reuse gate can check their impl cookies
|
|
icGlobalDtorName*: string # per-module backend: the C name of this
|
|
# module's global-destructor proc, recorded in
|
|
# the artifact's meta head so the main module's
|
|
# `cg` — a different process — can call it
|
|
icDataDefs*: seq[tuple[cname, nifname: string]]
|
|
# C names of data definitions (consts, globals,
|
|
# RTTI) this TU embeds plus their NIF symbol
|
|
# names (empty for RTTI, which has no symbol);
|
|
# recorded in the cnif artifact so a later run
|
|
# can reuse the TU and re-demand definitions
|
|
# that cached TUs still reference
|
|
g*: BModuleList
|
|
|
|
template config*(m: BModule): ConfigRef = m.g.config
|
|
template config*(p: BProc): ConfigRef = p.module.g.config
|
|
template vccAndC*(p: BProc): bool = p.module.config.cCompiler == ccVcc and p.module.config.backend == backendC
|
|
|
|
proc delayedCodegen*(m: BModule): bool {.inline.} =
|
|
useAliveDataFromDce in m.flags or m.config.globalOptions.contains(optCompress)
|
|
|
|
proc includeHeader*(this: BModule; header: string) =
|
|
if not this.headerFiles.contains header:
|
|
this.headerFiles.add header
|
|
|
|
proc s*(p: BProc, s: TCProcSection): var Builder {.inline.} =
|
|
# section in the current block
|
|
result = p.blocks[^1].sections[s]
|
|
|
|
proc procSec*(p: BProc, s: TCProcSection): var Builder {.inline.} =
|
|
# top level proc sections
|
|
result = p.blocks[0].sections[s]
|
|
|
|
proc initBlock*(): TBlock =
|
|
result = TBlock()
|
|
for i in low(result.sections)..high(result.sections):
|
|
result.sections[i] = newBuilder("")
|
|
|
|
proc newProc*(prc: PSym, module: BModule): BProc =
|
|
result = BProc(
|
|
prc: prc,
|
|
module: module,
|
|
optionsStack: if module.initProc != nil: module.initProc.optionsStack
|
|
else: @[],
|
|
options: if prc != nil: prc.options
|
|
else: module.config.options,
|
|
blocks: @[initBlock()],
|
|
sigConflicts: initCountTable[string]())
|
|
if optQuirky in result.options:
|
|
result.flags = {nimErrorFlagDisabled}
|
|
|
|
proc newModuleList*(g: ModuleGraph): BModuleList =
|
|
BModuleList(typeInfoMarker: initTable[SigHash, tuple[str: Rope, owner: int32]](),
|
|
config: g.config, graph: g, nimtvDeclared: initIntSet())
|
|
|
|
iterator cgenModules*(g: BModuleList): BModule =
|
|
for m in g.modulesClosed:
|
|
# iterate modules in the order they were closed
|
|
yield m
|