diff --git a/src/hastur.nim b/src/hastur.nim index 94effa823..8a718f1b0 100644 --- a/src/hastur.nim +++ b/src/hastur.nim @@ -1897,16 +1897,14 @@ proc bootCarryTools(): seq[string] = result = @BootCarryTools if bootNative: result.add BootNativeTools -const NativeBootReady = false - ## OFF until nativenif master carries arkham's register-allocator fixes. - ## The inter-module inliner honours `.inline` without a size cap, so a - ## `.inline` cascade can hand arkham a basic block whose live set exceeds - ## what its allocator can place, and stage 1 dies with "no staging register - ## available for a spill in proc semBodyCheckBody". That is an arkham bug — - ## it must spill, not give up — and it is fixed on nativenif's `araq-oor` - ## ("make the transient staging picks total"), not yet on the master CI - ## checks out. Flip this back to `true` once it is there; the C-backend boot - ## below is the whole self-host gate meanwhile. +const NativeBootReady = true + ## Requires a nativenif checkout whose arkham holds each emit step's demand + ## inside the transient-register budget (the `semBodyCheckBody` staging + ## exhaustion: binFold's pick-before-premat, emitCondValue2's early result + ## hold, and a staging pick blind to a free temp pool). When stage 1 dies + ## with "no staging register available", rebuild arkham/nifasm from a + ## nativenif master that carries those fixes — or flip this back to `false` + ## and the C-backend boot below is the whole self-host gate meanwhile. proc useNativeBoot(): bool = ## linux/amd64 is the platform the native backend is complete on: x86-64 diff --git a/src/hexer/intramodinliner.nim b/src/hexer/intramodinliner.nim index 959a20926..04e0d3fac 100644 --- a/src/hexer/intramodinliner.nim +++ b/src/hexer/intramodinliner.nim @@ -11,8 +11,20 @@ ## ## Restrictions in this first cook: ## - statement-position calls only (call as a direct stmts child); -## - single-return procs only (no `(ret …)` mid-body); -## - `.inline` pragma callees only (threshold == 0 in `InlineInfo`). +## - single-return procs only (no `(ret …)` mid-body). +## +## The inlining POLICY is size-driven, not annotation-driven (see +## `computeInlineInfo`): a body of at most `InlineTinyBound` tokens is always +## spliced — that covers forwarders, accessors and hooks whether or not the +## author wrote `.inline` — a `.noinline` proc never is, and anything bigger +## goes through the per-call-site weighted-score heuristic (`shouldInline`) +## whose threshold grows with the body size, so a big body needs ever juicier +## arguments (literals feeding conditions) to be worth its bulk. The +## `.inline` annotation itself is deliberately IGNORED here: it keeps its +## emission meaning (body shipped to importers, `static inline` in C) but no +## longer forces the splice, so an ill-considered `.inline` on a fat proc +## cannot blow the program up anymore, and a proc nobody thought to annotate +## still inlines when it is trivially cheap. ## ## Two passes share this machinery, one per pipeline stage, and each stays in ## the file format of its stage: @@ -22,10 +34,12 @@ ## is the one that crosses module borders: `loadForeign` lazy-loads the ## callee's `.c.nif`. ## -## Either way the body and the callee's `(inline THRESHOLD w…)` pragma -## annotation come out of the same file; the annotation is what -## `intraModuleInline` wrote at the hexer stage. There is no size cap: -## `.inline` is honoured whatever the body costs. +## Either way the decision is derived from the same file the body comes out +## of: `indexProcBodies` measures each proc right after the module is parsed, +## so what is scored is exactly what would be spliced (hexer's flattening of +## tiny bodies happens *before* the `.c.nif` is written — a proc that grows +## past the bound by having its own callees spliced into it is re-measured, +## and demoted, by every importer). ## ## The splice introduces a `(scope …)` block, declares one fresh `(var)` ## per parameter initialised from the argument, renames every local in @@ -45,10 +59,12 @@ type weights*: InlineWeights guardThreshold*: int guards*: InlineWeights + size*: int ## body token count; what a splice would cost ModuleAnalysis = object - ## Hexer-stage scratch: what `analyzeModule` computes so - ## `annotateInlinePragmas` can write it into the procs. Importers never - ## see this type — they read the annotation back with `readInlinePragma`. + ## Hexer-stage scratch: the threshold-0 procs `analyzeModule` found, so + ## `intraModuleInline` knows which bodies to flatten. Importers never see + ## this type — they re-derive the same information from the `.c.nif` they + ## parse anyway (`indexProcBodies`). ## (Not exported: `dce1` has an unrelated `ModuleAnalysis` and both ## modules are imported together by `pipeline`.) inlineInfo: Table[SymId, InlineInfo] @@ -56,6 +72,28 @@ type const DefaultInlineInfo* = InlineInfo(threshold: 100, weights: @[], guardThreshold: 100, guards: @[]) + InlineTinyBound* = 100 + ## Bodies of at most this many tokens are spliced unconditionally: at that + ## size the body is on the order of the call sequence it replaces (a + ## forwarder, an accessor with its assert, a hook's nil-test-and-call), so + ## inlining cannot lose. Measured evidence for the value: capping splices + ## at 100 tokens on a full nimsem build shrank the optimized IR 6x and the + ## binary 4x while the produced compiler ran slightly FASTER — beyond this + ## size, inlining pays icache, not wins. + InlineNeverBound* = 10000 + ## Thresholds at or above this mean "never" (`.noinline`). + InlineWeightCap* = 150 + ## Ceiling for a single parameter's weight. The weight walk adds the use + ## context's value per occurrence, so an uncapped weight grows with the + ## body — and since the threshold also grows with the body (`size div 4`), + ## the two cancel and ANY body whose params appear in conditions more than + ## ~once per 100 tokens would inline at every call site. The benefit of + ## substituting one argument does not scale with body size (folding a + ## branch is worth the branch, not the whole proc), so the estimate must + ## not either: with the cap, a body of size S needs on the order of + ## S/(4*150) max-weight literal arguments to inline — big bodies need + ## several genuinely decisive arguments, huge bodies effectively never + ## qualify. proc shouldInline*(info: InlineInfo; argScores: openArray[int]): bool = var sum = 0 @@ -64,41 +102,137 @@ proc shouldInline*(info: InlineInfo; argScores: openArray[int]): bool = sum += (info.weights[i] * score) div 100 result = sum >= info.threshold -proc readInlinePragma*(pragmas: Cursor; outInfo: var InlineInfo): bool = - ## Recover the `InlineInfo` `intraModuleInline` wrote into the proc's own - ## `.inline` pragma as `(inline THRESHOLD w…)`. Exactly the transport - ## `funcsummary`'s `(smry …)` uses and `readSummaryPragma` reads back: the - ## per-proc information travels *with the proc*, so an importer recovers it - ## from the same module file it already parsed for the body — no sidecar - ## section, and no way for the two to disagree. - ## - ## Returns false when the proc has no `.inline` pragma, which leaves the - ## caller's `DefaultInlineInfo` (threshold 100) in place. - if not pragmas.isTagLit: return false +proc collectParamSyms(params: Cursor): seq[SymId] = + result = @[] + if not params.isTagLit: return @[] + var p = params + p.into: + while p.hasMore: + if p.substructureKind == ParamU: + var q = p + inc q + if q.isSymbolDef: + result.add q.symId + skip p + +proc hasVarargsParam(params: Cursor): bool = + ## A `(varargs)` parameter cannot be bound to a `(var …)` at a splice site + ## (the type has no size), so such procs are never inlined. result = false - var p = pragmas - p.peekInto: # early-out on the first `.inline` + if not params.isTagLit: return false + var p = params + p.into: while p.hasMore: - if p.isTagLit and p.pragmaKind == InlineP: - # `.inline` alone already means threshold 0 ("always"); the annotation - # overrides that and appends one weight per parameter. - outInfo = InlineInfo(threshold: 0, weights: @[], - guardThreshold: DefaultInlineInfo.guardThreshold, - guards: @[]) - var seenThreshold = false - p.into: - while p.hasMore: - if p.kind == IntLit: - if not seenThreshold: - outInfo.threshold = int(p.intVal) - seenThreshold = true - else: - outInfo.weights.add int(p.intVal) - skip p - result = true - break + if p.substructureKind == ParamU: + var q = p + inc q # into the param: at the name + if q.isSymbolDef: + inc q # past name + skip q # past pragmas + if q.typeKind == VarargsT: result = true skip p +proc weightOfUse(n: Cursor): int = + case n.exprKind + of EqC, NeqC, LeC, LtC: 30 + of AddC, SubC, MulC, DivC, ModC, ShrC, ShlC, + BitandC, BitorC, BitxorC, BitnotC, NegC, + AndC, OrC, NotC: 20 + of AtC, PatC: 40 + of CallC: 10 + else: + case n.stmtKind + of IfS, WhileS, CaseS, IteS, ItecS, LoopS: 50 + of CallS: 10 + else: 0 + +proc walkInlineWeights(n: var Cursor; params: Table[SymId, int]; + weights: var seq[int]; inherited: int) = + case n.kind + of Symbol: + if params.hasKey(n.symId): + weights[params.getOrQuit(n.symId)] += inherited + inc n + of TagLit: + let w = max(inherited, weightOfUse(n)) + n.into: + while n.hasMore: + walkInlineWeights(n, params, weights, w) + else: + inc n + +proc tokenCountAux(n: var Cursor): int = + case n.kind + of TagLit: + result = 1 + n.into: + while n.hasMore: + result += tokenCountAux(n) + else: + result = 1 + inc n + +proc tokenCount(n: Cursor): int = + ## Tokens in the subtree rooted at `n` (closing parens not counted — they + ## may be virtual anyway). A stable cost measure for the policy below. + var c = n + result = tokenCountAux(c) + +proc computeInlineInfo*(procDecl: Cursor): InlineInfo = + ## The whole inlining policy, derived from the proc decl itself: + ## - no body / `.noinline` → never (an `InlineNeverBound` threshold); + ## - body ≤ `InlineTinyBound` tokens → always (threshold 0); + ## - anything bigger → the weighted-score heuristic, with a threshold + ## that grows with the body size (`max(100, size div 4)`), so only a + ## moderately-sized body with high-value arguments (literals feeding + ## conditions or index expressions) clears the bar. + ## The `.inline` annotation is NOT consulted — see the module docs. + result = DefaultInlineInfo + var p = procDecl + let pd = takeProcDecl(p) + if not pd.body.isTagLit: + result.threshold = InlineNeverBound # extern/no body: nothing to splice + return + if pd.pragmas.isTagLit: + var pr = pd.pragmas + pr.into: # scan all pragmas (no early break: the + while pr.hasMore: # `into` epilogue needs the scope drained) + if pr.isTagLit and pr.pragmaKind in {NoinlineP, ImportcP, ImportcppP, + AssemblerP}: + # importc: the decl's `(stmts .)` "body" is a PLACEHOLDER — the real + # code is external. Splicing it deletes the call (measured: memfiles + # inlined posix `open`'s empty shell and never called open(2)). + # assembler: the body is machine-level (register-pinned locals, 1:1 + # instructions) — meaningless spliced into ordinary code, and the + # splice strands `{.register.}` pragmas where no backend accepts + # them (measured: tcbackend's firstBit spliced + DCE'd, so the C + # backend saw a bare register-pinned local instead of rejecting the + # assembler proc). + result.threshold = InlineNeverBound + skip pr + if result.threshold >= InlineNeverBound: + return + if hasVarargsParam(pd.params): + result.threshold = InlineNeverBound + return + + let params = collectParamSyms(pd.params) + result.weights = newSeq[int](params.len) + let size = tokenCount(pd.body) + result.size = size + if size <= InlineTinyBound: + result.threshold = 0 + else: + result.threshold = max(DefaultInlineInfo.threshold, size div 4) + var lookup = initTable[SymId, int]() + for i, s in params: + lookup[s] = i + if lookup.len > 0: + var body = pd.body + walkInlineWeights(body, lookup, result.weights, 0) + for w in mitems(result.weights): + w = min(w, InlineWeightCap) + type ForeignModule* = object buf*: TokenBuf @@ -114,6 +248,16 @@ type src: ptr TokenBuf # the module's parsed buffer xnifDir: string # directory holding the `.c.nif`s maxDepth*: int # 0 = unlimited; cross-module mode sets a cap + growthLeft*: int + # Remaining tokens the proc currently being walked may gain from + # splices. Set per top-level `(proc …)` from `growthBudget` (a caller + # may roughly double), decremented by each committed splice — including + # the splices `trIntra` performs while re-walking spliced content, so a + # depth-N cascade draws from the same pot. This is the hard backstop + # that keeps program growth linear no matter what the per-call + # heuristic thinks: without it a chain of individually-approved + # splices compounds multiplicatively (measured: 8.4x IR blowup and + # multi-GB hexer RSS on nimsem). foreign: Table[string, ref ForeignModule] # Cached cross-module bodies. `ref` so growing the table doesn't # invalidate cursors that point into a previously-fetched buffer. @@ -141,17 +285,26 @@ proc initInlinerCtx*(moduleSuffix: string; src: ptr TokenBuf; ownInfo: initTable[SymId, InlineInfo](), xnifDir: xnifDir, maxDepth: maxDepth, + growthLeft: high(int), counterPrefix: counterPrefix, foreign: initTable[string, ref ForeignModule](), inProgress: initHashSet[SymId]()) +proc growthBudget*(bodySize: int): int = + ## How many spliced tokens a proc of `bodySize` may absorb: it may about + ## double, and small procs get a floor so a forwarder can still swallow a + ## couple of tiny callees. + max(1000, bodySize) + proc indexProcBodies(buf: var TokenBuf; bodies: var Table[SymId, int]; infos: var Table[SymId, InlineInfo]) = ## Walks the top-level `(stmts …)` and records `(proc :sym …)` decls - ## by sym → byte offset into `buf`, along with the `(inline THRESHOLD w…)` - ## annotation each inlinable proc carries in its pragmas. Reading the - ## annotation costs nothing extra here — we are already at the decl and - ## `takeProcDecl` only skips subtrees, it does not walk the body. + ## by sym → byte offset into `buf`, along with each proc's `InlineInfo`, + ## computed right here from the body we are indexing (`computeInlineInfo` + ## walks it once — a linear pass over a buffer we just parsed anyway). No + ## pragma transport is involved, so own-module and foreign bodies go + ## through the identical policy, and the size that is scored is the size + ## of the exact body a splice would copy. var n = beginRead(buf) if n.stmtKind == StmtsS: n.into: @@ -160,10 +313,9 @@ proc indexProcBodies(buf: var TokenBuf; bodies: var Table[SymId, int]; let nameCur = n.childCursor # the (proc :sym …) name child if nameCur.isSymbolDef: bodies[nameCur.symId] = cursorToPosition(buf, n) - var probe = n - let d = takeProcDecl(probe) - var info = DefaultInlineInfo - if readInlinePragma(d.pragmas, info): + let info = computeInlineInfo(n) + if info.threshold == 0 or + (info.threshold < InlineNeverBound and info.weights.len > 0): infos[nameCur.symId] = info skip n @@ -220,11 +372,9 @@ proc lookupBody(c: var InlinerCtx; calleeSym: SymId; outCur: var Cursor): bool = if not loadForeign(c, modul): return false let fm = c.foreign.getOrQuit(modul) if calleeSym notin fm.bodies: return false - # No size cap: `.inline` is the programmer saying "inline this", and the - # only bodies that reach here are the ones that carry it (a proc without - # the pragma keeps `DefaultInlineInfo`, threshold 100 with no weights, and - # `shouldInlineCall` declines it). Refusing a body for being big would make - # `.inline` mean "inline if the compiler feels like it". + # No further vetting here: the only bodies that reach this point already + # passed `shouldInlineCall`, i.e. the size-driven policy in + # `computeInlineInfo` (tiny → always, big → scored, `.noinline` → never). outCur = cursorAt(fm.buf, fm.bodies.getOrQuit(calleeSym)) result = true @@ -291,85 +441,53 @@ proc lookupInlineInfo(c: var InlinerCtx; calleeSym: SymId): InlineInfo = result = c.foreign.getOrQuit(modul).inlineInfo.getOrDefault(calleeSym, DefaultInlineInfo) +proc argContainsConstructor(callNode: Cursor): bool = + ## `(oconstr/aconstr …)` anywhere in an argument. The C backend renders an + ## address-taken aggregate constructor as a block-scope compound literal; + ## a splice wraps its param bindings in a `(scope …)` — a C block — cutting + ## that literal's lifetime short whenever its address escapes the splice + ## (measured: `static Shape[N]` params — the openArray built over + ## `&(Shape){…}.bounds` read dead stack after the scope closed). Until the + ## splicer hoists such temporaries out of its scope, decline the site. + proc walk(n: var Cursor): bool = + case n.kind + of TagLit: + if n.exprKind in {OconstrC, AconstrC}: + skip n + return true + result = false + n.into: + while n.hasMore: + if walk(n): result = true + else: + result = false + inc n + var a = callNode + result = false + a.into: + skip a # past the callee sym + while a.hasMore: + if walk(a): result = true + proc shouldInlineCall(c: var InlinerCtx; calleeSym: SymId; callNode: Cursor): bool = ## Decides whether to splice a call to `calleeSym` at this call site. - ## `.inline` (threshold 0) always wins; `.noinline` (threshold - ## ≥ 10000) always loses; everything else goes through the per-call - ## weighted-score heuristic against the proc's `InlineInfo`. + ## Tiny bodies (threshold 0) always win; `.noinline` / bodiless procs + ## (threshold ≥ `InlineNeverBound`, or no stored info at all) always lose; + ## everything else goes through the per-call weighted-score heuristic + ## against the proc's `InlineInfo`. let info = lookupInlineInfo(c, calleeSym) + if info.threshold >= InlineNeverBound: return false + if info.size > c.growthLeft: return false # caller's growth budget is spent + if argContainsConstructor(callNode): return false if info.threshold == 0: return true - if info.threshold >= 10000: return false let scores = computeArgScores(callNode) result = shouldInline(info, scores) -proc collectParamSyms(params: Cursor): seq[SymId] = - result = @[] - if not params.isTagLit: return @[] - var p = params - p.into: - while p.hasMore: - if p.substructureKind == ParamU: - var q = p - inc q - if q.isSymbolDef: - result.add q.symId - skip p - -proc weightOfUse(n: Cursor): int = - case n.exprKind - of EqC, NeqC, LeC, LtC: 30 - of AddC, SubC, MulC, DivC, ModC, ShrC, ShlC, - BitandC, BitorC, BitxorC, BitnotC, NegC, - AndC, OrC, NotC: 20 - of AtC, PatC: 40 - of CallC: 10 - else: - case n.stmtKind - of IfS, WhileS, CaseS, IteS, ItecS, LoopS: 50 - of CallS: 10 - else: 0 - -proc walkInlineWeights(n: var Cursor; params: Table[SymId, int]; - weights: var seq[int]; inherited: int) = - case n.kind - of Symbol: - if params.hasKey(n.symId): - weights[params.getOrQuit(n.symId)] += inherited - inc n - of TagLit: - let w = max(inherited, weightOfUse(n)) - n.into: - while n.hasMore: - walkInlineWeights(n, params, weights, w) - else: - inc n - -proc computeInlineInfo*(procDecl: Cursor): InlineInfo = - result = DefaultInlineInfo - var p = procDecl - let pd = takeProcDecl(p) - let params = collectParamSyms(pd.params) - result.weights = newSeq[int](params.len) - - var hasInline = false - if pd.pragmas.isTagLit: - var pr = pd.pragmas - pr.into: # scan all pragmas (no early break: the - while pr.hasMore: # `into` epilogue needs the scope drained) - if pr.isTagLit and pr.pragmaKind == InlineP: - hasInline = true - skip pr - if not hasInline: - return - - result.threshold = 0 - var lookup = initTable[SymId, int]() - for i, s in params: - lookup[s] = i - if lookup.len > 0 and pd.body.isTagLit: - var body = pd.body - walkInlineWeights(body, lookup, result.weights, 0) +proc chargeSplice(c: var InlinerCtx; calleeSym: SymId) = + ## Book the committed splice against the current caller's growth budget. + let size = lookupInlineInfo(c, calleeSym).size + c.growthLeft = max(0, c.growthLeft - max(size, 1)) proc analyzeModule(buf: var TokenBuf): ModuleAnalysis = result = ModuleAnalysis(inlineInfo: initTable[SymId, InlineInfo]()) @@ -818,6 +936,26 @@ proc seedRenameFromBody(c: var InlinerCtx; body: Cursor; var n = body seedRenameWalk(c, n, rename) +when defined(inlinerStats): + import std / [algorithm, syncio] + var inlinerStats*: Table[string, tuple[count, tokens: int]] + + proc recordSplice(calleeSym: SymId; tokens: int) = + let nm = pool.syms[calleeSym] + var e = inlinerStats.getOrDefault(nm) + inc e.count + e.tokens += tokens + inlinerStats[nm] = e + + proc dumpInlinerStats*(label: string) = + var rows: seq[(int, int, string)] = @[] + for k, v in inlinerStats: + rows.add (v.tokens, v.count, k) + rows.sort(SortOrder.Descending) + stderr.writeLine "--- inliner stats " & label & " ---" + for (t, cnt, k) in rows: + stderr.writeLine $t & "\t" & $cnt & "\t" & k + proc trySplice*(c: var InlinerCtx; dest: var TokenBuf; n: var Cursor): int = ## If `n` points at a `(call f arg…)` statement we can inline, emit ## the splice into `dest`, advance `n` past the call, and return the @@ -893,6 +1031,8 @@ proc trySplice*(c: var InlinerCtx; dest: var TokenBuf; n: var Cursor): int = # Advance the caller's cursor past the original call. n = entry skip n + chargeSplice c, calleeSym + when defined(inlinerStats): recordSplice(calleeSym, dest.len) result = 1 # one `(scope …)` emitted proc trySpliceVarInit*(c: var InlinerCtx; dest: var TokenBuf; n: var Cursor): int = @@ -1011,6 +1151,8 @@ proc trySpliceVarInit*(c: var InlinerCtx; dest: var TokenBuf; n: var Cursor): in # Advance past the original var decl. n = entry skip n + chargeSplice c, calleeSym + when defined(inlinerStats): recordSplice(calleeSym, dest.len) result = 2 # `(var …)` + `(scope …)` # ---- Condition-splice: inline body straight into an `if`/`elif` guard ---- @@ -1260,9 +1402,221 @@ proc trySpliceCond*(c: var InlinerCtx; dest: var TokenBuf; n: var Cursor; n = entry skip n # past var skip n # past if + chargeSplice c, cSym calleeSym = cSym result = 1 +# ---- Splice-time branch pruning ---- + +type + CondVal = enum + condUnknown, condFalse, condTrue + +proc negated(v: CondVal): CondVal = + case v + of condTrue: condFalse + of condFalse: condTrue + of condUnknown: condUnknown + +proc litSame(a, b: Cursor): CondVal = + ## Literal identity over the operand kinds `isSubstitutableArg` splices; + ## anything else — including mixed literal kinds — stays `condUnknown`. + if a.kind == IntLit and b.kind == IntLit: + (if a.intVal == b.intVal: condTrue else: condFalse) + elif a.kind == UIntLit and b.kind == UIntLit: + (if a.uintVal == b.uintVal: condTrue else: condFalse) + elif a.kind == CharLit and b.kind == CharLit: + (if a.charLit == b.charLit: condTrue else: condFalse) + elif a.isTagLit and b.isTagLit and + a.exprKind in {NilC, TrueC, FalseC} and + b.exprKind in {NilC, TrueC, FalseC}: + (if a.exprKind == b.exprKind: condTrue else: condFalse) + else: + condUnknown + +proc condVal(n: Cursor): CondVal = + ## What a guard evaluates to once argument substitution made it literal: + ## `(neq (nil) (nil))` from a spliced `if c != nil` with `c := nil`, or the + ## `(not (eq …))` a nested `!=` forwarder splice leaves behind. `and`/`or` + ## fold only when BOTH operands decide, so no operand whose evaluation the + ## fold would discard is ever left unjudged. + result = condUnknown + if not n.isTagLit: return + case n.exprKind + of TrueC: result = condTrue + of FalseC: result = condFalse + of NotC: + let arg = n.childCursor + if arg.hasMore: + result = negated(condVal(arg)) + of EqC, NeqC: + let a = n.childCursor + if a.hasMore: + var b = a + skip b + if b.hasMore: + let same = litSame(a, b) + result = (if n.exprKind == EqC: same else: negated(same)) + of AndC, OrC: + let a = n.childCursor + if a.hasMore: + var b = a + skip b + if b.hasMore: + let l = condVal(a) + let r = condVal(b) + if l != condUnknown and r != condUnknown: + if n.exprKind == AndC: + result = (if l == condTrue and r == condTrue: condTrue else: condFalse) + else: + result = (if l == condTrue or r == condTrue: condTrue else: condFalse) + else: discard + +proc hasAnyDef(n: Cursor): bool = + ## Any SymbolDef in the subtree: a `(lab :L)` someone may jump to, or a + ## `(var :v …)` declaration later reachable code may reference. Either + ## makes a dead statement unsafe to drop. + case n.kind + of SymbolDef: + result = true + of TagLit: + result = false + var it = n.childCursor + while it.hasMore: + if hasAnyDef(it): return true + skip it + else: + result = false + +proc emitPruned(dest: var TokenBuf; n: var Cursor) = + ## Copy one subtree, deleting every `(elif …)` arm whose guard `condVal` + ## decided. This is a CORRECTNESS duty, not an optimization: the false arm + ## of a spliced body may no longer type-check at all — `if c != nil: …c.f…` + ## inlined with `c := nil` keeps a `(deref (nil))` there — and a typed + ## backend (arkham) must never see it, so the splice that manufactured the + ## constant guard deletes the arm too. An `(elif (true) …)` arm demotes to + ## the `if`'s final `(else …)` (the arms after it can never run); an `if` + ## with no live arm left contributes its `else` body, or nothing. + ## + ## A decided arm may be deleted with its labels: a jmp into a sibling + ## branch is not part of the final IR (try/except lowers to a FLAT goto + ## sequence), so any `(lab …)` inside the arm is jumped to only from + ## inside it — this inliner's own returnLabel pattern — and the arm takes + ## the label and its jumps with it. + case n.kind + of TagLit: + if n.stmtKind == IfS: + # Peek pass over the arms: what survives? The cursors index into the + # buffer `n` reads, which outlives the re-emit below. + var kept: seq[Cursor] = @[] # elifs with undecided guards + var taken = default(Cursor) # first `(true)` elif, or the else + var takenIsElif = false + var haveTaken = false + var dropped = false # anything decided at all? + var probe = n + probe.into: + while probe.hasMore: + let sk = probe.substructureKind + if haveTaken: + dropped = true # dead branch after a taken one + elif sk == ElifU: + case condVal(probe.childCursor) + of condTrue: + taken = probe; takenIsElif = true; haveTaken = true; dropped = true + of condFalse: + dropped = true + of condUnknown: + kept.add probe + elif sk == ElseU: + taken = probe; takenIsElif = false; haveTaken = true + else: + kept.add probe # unexpected shape: keep verbatim + skip probe + if not dropped: + # Nothing decided at this level: keep the `if`, but still recurse + # into the branch bodies (they may contain prunable ifs). + dest.addParLe(n.cursorTagId, n.info) + n.into: + while n.hasMore: + emitPruned(dest, n) + dest.addParRi() + return + if kept.len == 0: + # No undecided elifs before the taken branch: the whole `if` + # collapses to the taken branch's body (or to nothing). + if haveTaken: + var b = taken + b.into: + if takenIsElif and b.hasMore: skip b # past the guard + while b.hasMore: + emitPruned(dest, b) + skip n + return + # Some undecided elifs survive: rebuild the `if` from them, a taken + # `(true)` elif demoted to the terminal `(else …)`. + dest.addParLe(n.cursorTagId, n.info) + for arm in kept: + var a = arm + dest.addParLe(a.cursorTagId, a.info) + a.into: + while a.hasMore: + emitPruned(dest, a) + dest.addParRi() + if haveTaken: + let btag = (if takenIsElif: TagId(ElseU) else: taken.cursorTagId) + dest.addParLe(btag, taken.info) + var b = taken + b.into: + if takenIsElif and b.hasMore: skip b # past the guard + while b.hasMore: + emitPruned(dest, b) + dest.addParRi() + dest.addParRi() + skip n + elif n.stmtKind in {StmtsS, ScopeS}: + # Drop UNREACHABLE statements: after an unconditional `(jmp …)`/`(ret …)` + # nothing executes until the next `(lab …)`, so def-free statements in + # between are dead. The value-splice epilogue produces exactly this — + # a callee whose every path returns via `(asgn dest X) (jmp RL)` leaves + # the trailing `dest = result` self-copy dead with `result` never + # written — and a typed backend verifier rightly rejects the dead read. + # A statement that defines anything is kept and ends the dead region + # (something can jump into it and fall out of it). + dest.addParLe(n.cursorTagId, n.info) + var unreachable = false + n.into: + while n.hasMore: + let sk = n.stmtKind + if sk == LabS: + unreachable = false + dest.takeTree n + elif unreachable and not hasAnyDef(n): + skip n # dead: drop + else: + if unreachable: unreachable = false + emitPruned(dest, n) + if sk in {JmpS, RetS}: unreachable = true + dest.addParRi() + elif n.stmtKind == NoStmt and n.substructureKind == NoSub: + # An expression subtree cannot contain statements, hence no `if` arms. + dest.takeTree n + else: + dest.addParLe(n.cursorTagId, n.info) + n.into: + while n.hasMore: + emitPruned(dest, n) + dest.addParRi() + else: + dest.takeTree n + +proc prunedInto(dest: var TokenBuf; expanded: var TokenBuf) = + ## Emit every top-level subtree of `expanded` into `dest` with the decided + ## `if` arms deleted (`emitPruned`). + var pruner = beginRead(expanded) + while pruner.hasMore: + emitPruned(dest, pruner) + endRead(pruner) + # ---- Same-module inliner pass (called from hexer.nim) ---- proc trIntra*(c: var InlinerCtx; dest: var TokenBuf; n: var Cursor) = @@ -1297,12 +1651,18 @@ proc trIntra*(c: var InlinerCtx; dest: var TokenBuf; n: var Cursor) = if nEmitted > 0: if calleeSym != SymId(0): c.inProgress.incl calleeSym + # Nested splices first, into a scratch buffer; THEN prune the + # branches the substituted arguments decided — only after the + # nested walk are inlined guards (`!=` forwarders) reduced to + # the literal comparisons `condVal` can judge. + var expanded = createTokenBuf(spliced.len) var inner = beginRead(spliced) for _ in 0 ..< nEmitted: - trIntra(c, dest, inner) + trIntra(c, expanded, inner) endRead(inner) if calleeSym != SymId(0): c.inProgress.excl calleeSym + prunedInto(dest, expanded) continue if n.isTagLit and n.stmtKind == VarS: # `(var :tmp T (call …))` immediately guarding an `if` — fold the @@ -1313,11 +1673,13 @@ proc trIntra*(c: var InlinerCtx; dest: var TokenBuf; n: var Cursor) = let nEmitted = trySpliceCond(c, spliced, n, condCallee) if nEmitted > 0: c.inProgress.incl condCallee + var expanded = createTokenBuf(spliced.len) var inner = beginRead(spliced) for _ in 0 ..< nEmitted: - trIntra(c, dest, inner) + trIntra(c, expanded, inner) endRead(inner) c.inProgress.excl condCallee + prunedInto(dest, expanded) continue trIntra(c, dest, n) dest.addParRi() @@ -1325,6 +1687,22 @@ proc trIntra*(c: var InlinerCtx; dest: var TokenBuf; n: var Cursor) = # `(var :tmp (call …))` is the bound form `xelim` # and the new nifcgen complex-init path emit; route it through the # var-init splice. Other locals copy verbatim. + if sk == ProcS: + # Entering a proc decl: give it its own growth budget, sized from its + # body, and restore the enclosing one afterwards (procs are top-level + # in NIFC, but the restore keeps this correct either way). + var probe = n + let pd = takeProcDecl(probe) + let bodySize = (if pd.body.isTagLit: tokenCount(pd.body) else: 0) + let savedGrowth = c.growthLeft + c.growthLeft = growthBudget(bodySize) + dest.addParLe(n.cursorTagId, n.info) + into n: + while n.hasMore: + trIntra(c, dest, n) + dest.addParRi() + c.growthLeft = savedGrowth + return if sk == VarS: var probe = n inc probe @@ -1342,11 +1720,13 @@ proc trIntra*(c: var InlinerCtx; dest: var TokenBuf; n: var Cursor) = let nEmitted = trySpliceVarInit(c, spliced, n) if nEmitted > 0: c.inProgress.incl calleeSym + var expanded = createTokenBuf(spliced.len) var inner = beginRead(spliced) for _ in 0 ..< nEmitted: - trIntra(c, dest, inner) + trIntra(c, expanded, inner) endRead(inner) c.inProgress.excl calleeSym + prunedInto(dest, expanded) return dest.addParLe(n.cursorTagId, n.info) into n: @@ -1362,81 +1742,22 @@ proc trIntra*(c: var InlinerCtx; dest: var TokenBuf; n: var Cursor) = else: dest.takeTree n -proc emitPragmasWithInlineInfo(dest: var TokenBuf; pragmas: Cursor; info: InlineInfo) = - var p = pragmas - if not p.isTagLit: - dest.addSubtree p - return - - dest.addParLe(p.cursorTagId, p.info) - p.into: - while p.hasMore: - if p.isTagLit and p.pragmaKind == InlineP: - dest.addParLe(p.cursorTagId, p.info) - p.into: - dest.addIntLit info.threshold, p.endInfo - for w in info.weights: - dest.addIntLit w, p.endInfo - while p.hasMore: - skip p - dest.addParRi() - else: - dest.takeTree p - dest.addParRi() - -proc annotateInlinePragmas(dest: var TokenBuf; n: var Cursor; - infos: Table[SymId, InlineInfo]) = - case n.kind - of TagLit: - if n.stmtKind == ProcS: - let tag = n.cursorTagId - let info = n.info - let d = takeProcDecl(n) - dest.addParLe(tag, info) - dest.addSubtree d.name - let sym = d.name.symId - dest.addSubtree d.params - dest.addSubtree d.returnType - if infos.hasKey(sym): - emitPragmasWithInlineInfo(dest, d.pragmas, infos.getOrQuit(sym)) - else: - dest.addSubtree d.pragmas - dest.addSubtree d.body - dest.addParRi() - else: - dest.addParLe(n.cursorTagId, n.info) - n.into: - while n.hasMore: - annotateInlinePragmas(dest, n, infos) - dest.addParRi() - else: - dest.takeTree n - -proc annotateInlinePragmas(buf: var TokenBuf; infos: Table[SymId, InlineInfo]) = - if infos.len == 0: return - var n = beginRead(buf) - var dest = createTokenBuf(buf.len) - annotateInlinePragmas(dest, n, infos) - buf = ensureMove(dest) - proc intraModuleInline*(moduleSuffix: string; buf: var TokenBuf) = ## Same-module inliner pass run as the last step of hexer's `expand`, so - ## the `.x.nif` we publish has each `.inline` proc body already cascaded + ## the `.x.nif` we publish has each tiny proc body already cascaded ## against its same-module callees. An importer then pulls a flat body, and ## the cascade is walked once per module here instead of once per importer. + ## A body that grows past `InlineTinyBound` by this flattening is simply + ## re-measured — and demoted to the scored tier — by whoever parses the + ## published file (`indexProcBodies`), so the flattening cannot compound: + ## what importers splice is what they measured. ## - ## Measured on nimsem (126 modules), that redundancy is worth little: the - ## chains that matter run *across* modules (`nifcore` → `nifpools`), which - ## no same-module flattening can pre-expand, so the inter-module pass still - ## needs its depth and its cost is unchanged. Keep the numbers in mind - ## before spending anything more here — full rebuild 18.66s → 18.87s, - ## nimsem-on-system.nim 80ms → 81ms, binary −4KB. - ## - ## Also writes each `.inline` proc's computed `InlineInfo` into its own - ## pragma (`(inline THRESHOLD w…)`). That annotation is what importers read - ## back — see `readInlinePragma` — so no sidecar has to carry it. It is - ## written *before* the splice because the splice reads it back: `ownInfo` - ## is filled from the pragmas by `collectProcBodies`. + ## Measured on nimsem (126 modules), the flattening redundancy is worth + ## little: the chains that matter run *across* modules (`nifcore` → + ## `nifpools`), which no same-module flattening can pre-expand, so the + ## inter-module pass still needs its depth and its cost is unchanged. Keep + ## the numbers in mind before spending anything more here — full rebuild + ## 18.66s → 18.87s, nimsem-on-system.nim 80ms → 81ms, binary −4KB. ## ## `xnifDir` stays empty here on purpose. This module's own `.x.nif` is ## pre-DCE, so its generic instances and hexer-minted types still carry the @@ -1445,13 +1766,13 @@ proc intraModuleInline*(moduleSuffix: string; buf: var TokenBuf) = ## thing, while one copied *across* modules would not. Cross-module splicing ## therefore waits for the `.c.nif` (`shoggoth`'s inter-module pass). let ma = analyzeModule(buf) - annotateInlinePragmas(buf, ma.inlineInfo) if ma.inlineInfo.len == 0: return # Only the `.inline` bodies are flattened, not every call site in the module: # a call site here is one the importer's own pass would splice anyway, and # doing it twice only inflates the `.x.nif` everything downstream reads. - var ctx = initInlinerCtx(moduleSuffix, addr buf, counterPrefix = "h") + var ctx = initInlinerCtx(moduleSuffix, addr buf, maxDepth = 4, + counterPrefix = "h") collectProcBodies(ctx) var dest = createTokenBuf(buf.len + buf.len div 16) var n = beginRead(buf) @@ -1470,6 +1791,7 @@ proc intraModuleInline*(moduleSuffix: string; buf: var TokenBuf) = dest.addSubtree d.returnType dest.addSubtree d.pragmas var body = d.body + ctx.growthLeft = growthBudget(tokenCount(body)) trIntra(ctx, dest, body) dest.addParRi() else: diff --git a/src/hexer/lengcgen.nim b/src/hexer/lengcgen.nim index 50de2ba23..d7031f509 100644 --- a/src/hexer/lengcgen.nim +++ b/src/hexer/lengcgen.nim @@ -1008,6 +1008,7 @@ proc trProc(c: var EContext; dest: var TokenBuf; n: var Cursor; mode: TraverseMo trParams c, dest, n let pinfo = n.info + let procRaises = hasPragma(n, RaisesP) let prag = parsePragmas(c, dest, n) var genPragmas = openGenPragmas() @@ -1030,6 +1031,21 @@ proc trProc(c: var EContext; dest: var TokenBuf; n: var Cursor; mode: TraverseMo if AssemblerP in prag.flags: dest.addKey genPragmas, "assembler", pinfo + if NoreturnP in prag.flags and not procRaises: + # Leng has no noreturn pragma of its own; carry the fact as the existing + # `(attr "noreturn")`. The C backend renders it `__attribute__((noreturn))` + # (a codegen win in its own right), arkham skips unknown pragmas, and the + # optimizer's condition-elimination pass reads it to learn facts from the + # fall-through of assert/panic guards. + # + # NOT for `.raises` procs: under goto exceptions a raising "noreturn" proc + # (raiseOSError) RETURNS at the Leng level — it hands back an error code + # for the caller to propagate. Telling C it never returns made gcc delete + # the callers' error paths (a stage-2 boot miscompile), and it would + # mislead the fall-through learning the same way. Only a proc that + # genuinely diverges — exits or aborts — may carry the attribute. + dest.addKeyVal genPragmas, "attr", pool.strings.getOrIncl("noreturn"), pinfo + closeGenPragmas dest, genPragmas skip n # miscPos @@ -1976,7 +1992,23 @@ proc trRaise(c: var EContext; dest: var TokenBuf; n: var Cursor) = dest.addParRi(n.endInfo) proc trTry(c: var EContext; dest: var TokenBuf; n: var Cursor) = - # We only deal with the control flow here. + # We only deal with the control flow here. A `try` with handlers lowers to + # a FLAT goto sequence: + # + # # every raise inside became (jmp `exlab.N) + # # normal path only, see below + # (jmp `exend.N) + # (stmts (lab :`exlab.N) ) + # (lab :`exend.N) + # + # The handler must skip the normal path's finally — its own finally already + # ran at the raise site (finally statements are duplicated before every + # `raise`) — and the explicit jmp says so directly. The former shape parked + # the handler in a false-guarded `(elif)` of an `(if)` for the same effect, + # and every consumer paid for the pretense: arkham emitted a real + # materialize-and-`cmp 0` for a guard it cannot know is dead, and every + # branch-pruning pass needed a label-pinning rule to keep it from deleting + # a "dead" arm a jmp enters (see `branchPinned` in intramodinliner). let info = n.info let tryStart = n n = sub(n) @@ -1994,44 +2026,51 @@ proc trTry(c: var EContext; dest: var TokenBuf; n: var Cursor) = hasExcept = true trStmt c, dest, n - if hasExcept: - dest.addParLe IfS, n.info - + # The except clauses precede the finally in the tree, but the flat form + # emits the normal path (the finally) first: park their cursors, return + # for them after. A `raise` inside a handler or the finally must propagate + # PAST this try, not loop back to its own handler label, so the label is + # popped before either is translated. + var handlers: seq[Cursor] = @[] while n.substructureKind == ExceptU: - let lab = tryLab - dest.copyIntoKind ElifU, n.info: - dest.addParPair(FalseX, n.info) - dest.copyIntoKind StmtsS, n.info: - dest.addParLe("lab", n.info) - dest.addSymDef(lab, n.info) - dest.addParRi() - n.into: - if n.stmtKind == LetS: - trStmt c, dest, n - else: - skip n # skip `T` - # A `raise` (typed or bare) inside an except handler must propagate - # PAST this try, not loop back to its own handler label. Temporarily - # pop the label for the duration of the handler body so any nested - # `raise` uses the next outer label (or `return`). - c.exceptLabels.shrink oldLen - trStmt c, dest, n - c.exceptLabels.add tryLab + handlers.add n + skip n c.exceptLabels.shrink oldLen # Since we duplicated the finally statements before every `raise` statement we # know that when control flow reaches here, no error was raised. Hence we do not # need to add logic to re-raise an exception here. if n.substructureKind == FinU: - if hasExcept: - dest.addParLe ElseU, n.info n.into: trStmt c, dest, n - if hasExcept: - dest.addParRi() - n = tryStart; skip n + if hasExcept: + let endLab = pool.syms.getOrIncl("`exend." & $getTmpId(c)) + dest.addParLe("jmp", info) + dest.addSymUse(endLab, info) dest.addParRi() + for i in 0 ..< handlers.len: + var h = handlers[i] + let hinfo = h.info + dest.copyIntoKind StmtsS, hinfo: + if i == 0: + dest.addParLe("lab", hinfo) + dest.addSymDef(tryLab, hinfo) + dest.addParRi() + h.into: + if h.stmtKind == LetS: + trStmt c, dest, h + else: + skip h # skip `T` + trStmt c, dest, h + if i < handlers.len - 1: + dest.addParLe("jmp", hinfo) + dest.addSymUse(endLab, hinfo) + dest.addParRi() + dest.addParLe("lab", info) + dest.addSymDef(endLab, info) + dest.addParRi() + n = tryStart; skip n proc trStmt(c: var EContext; dest: var TokenBuf; n: var Cursor; mode = TraverseInner) = case n.kind diff --git a/src/hexer/lifter.nim b/src/hexer/lifter.nim index 65f4dc8fb..6d2f7f1d4 100644 --- a/src/hexer/lifter.nim +++ b/src/hexer/lifter.nim @@ -464,10 +464,37 @@ proc unravelObjFieldsForward(c: var LiftingCtx; n: var Cursor; paramA, paramB: T # copy the selector before case stmt, but destroy after case stmt unravelObjField c, selector, paramA, paramB, depth - c.dest.addParLe CaseU, info - var selectorField = takeLocal(n, SkipFinalParRi) let dest = accessObjField(c, paramA, selectorField.name) + + if c.op == attachedWasMoved: + # `=wasMoved` is called on memory that is not necessarily initialized: + # a proc's `result` slot receives `=wasMoved` followed by `=destroy` + # before its first assignment (see `some[T]` in `std/opt`). Dispatching + # on the discriminant READS that garbage, in BOTH hooks. The pair is + # self-neutralizing only while the two loads agree — `=wasMoved` clears + # the payload of whatever branch it read, so the `=destroy` behind it + # finds an empty one. Nothing guarantees they agree: an uninitialized + # load is `undef`, and the optimizer may materialize it independently + # per use. `=wasMoved` reading None while `=destroy` reads Some frees a + # `string` that was never constructed. So SELECT the branch instead of + # reading it: write the discriminant, then let the dispatch below clear + # exactly that branch. Every op a `=wasMoved` emits is a pure write, so + # the result is a valid, trivially-destroyable value either way. + var firstBranch = n + if firstBranch.substructureKind == OfU: + var ranges = sub(firstBranch) + if ranges.substructureKind == RangesU: + var val = sub(ranges) + # `of lo..hi` lists a `range` node rather than a plain value; the + # low bound selects the same branch and is a value we can assign. + if val.substructureKind == RangeU: + val = sub(val) + copyIntoKind c.dest, AsgnS, c.info: + copyTree c.dest, dest + copyTree c.dest, val + + c.dest.addParLe CaseU, info c.dest.add dest while n.hasMore: diff --git a/src/lengc/codegen.nim b/src/lengc/codegen.nim index 0f3e710bc..87408093a 100644 --- a/src/lengc/codegen.nim +++ b/src/lengc/codegen.nim @@ -367,11 +367,20 @@ proc parseProcPragmas(c: var GeneratedCode; n: var Cursor): PragmaInfo = else: error c.m, "expected proc pragmas but got: ", n -proc genSymDef(c: var GeneratedCode; n: Cursor; prag: PragmaInfo): string = +proc isBareImportProc(prag: PragmaInfo): bool {.inline.} = + ## `importc` proc with neither `header` nor `nodecl`: declared by US, under + ## its mangled name + `__asm__` label (collision-proof against header + ## prototypes for the same libc identifier — see `mangleSym`). + ImportcP in prag.flags and {HeaderP, NodeclP} * prag.flags == {} + +proc genSymDef(c: var GeneratedCode; n: Cursor; prag: PragmaInfo; + isProc = false): string = if n.kind == SymbolDef: let lit = n.symId if {ImportcP, ImportcppP, ExportcP} * prag.flags != {}: - if prag.extern != StrId(0): + if isProc and isBareImportProc(prag): + result = mangleToC(c.m.pool.syms[lit]) + elif prag.extern != StrId(0): result = c.m.pool.strings[prag.extern] else: result = c.m.pool.syms[lit] @@ -654,7 +663,7 @@ proc genProcDecl(c: var GeneratedCode; n: var Cursor; isExtern: bool) = c.add Comma if prag.attr != StrId(0): c.add "__attribute__((" & c.m.pool.strings[prag.attr] & ")) " - name = genSymDef(c, prc.name, prag) + name = genSymDef(c, prc.name, prag, isProc = true) c.add ParRi else: if prc.returnType.kind == DotToken: @@ -664,7 +673,7 @@ proc genProcDecl(c: var GeneratedCode; n: var Cursor; isExtern: bool) = c.add Space if prag.attr != StrId(0): c.add "__attribute__((" & c.m.pool.strings[prag.attr] & ")) " - name = genSymDef(c, prc.name, prag) + name = genSymDef(c, prc.name, prag, isProc = true) c.add ParLe @@ -684,6 +693,20 @@ proc genProcDecl(c: var GeneratedCode; n: var Cursor; isExtern: bool) = c.code.setLen signatureBegin elif InlineP notin prag.flags and (isExtern or {ImportcP, ImportcppP} * prag.flags != {}): # External/imported function without body - just prototype + if isBareImportProc(prag): + # Bind the mangled identifier to the real symbol. The identifier never + # collides with a header's prototype for the same libc function, which + # matters since inliner splices carry bare-importc references into + # arbitrary modules (measured: `write` vs in threads/cps). + var asmName = "" + if prag.extern != StrId(0): + asmName = c.m.pool.strings[prag.extern] + else: + asmName = c.m.pool.syms[prc.name.symId] + extractBasename(asmName) + c.add " __asm__(NIM_ASM_PREFIX " + c.add makeCString(asmName) + c.add ")" for i in signatureBegin ..< c.code.len: c.protos.add c.code[i] c.protos.add Token Semicolon diff --git a/src/lengc/cprelude.nim b/src/lengc/cprelude.nim index f74fd5eaa..9870162d0 100644 --- a/src/lengc/cprelude.nim +++ b/src/lengc/cprelude.nim @@ -28,6 +28,15 @@ const typedef unsigned char NB8; // best effort #endif +/* Assembler-name prefix for `__asm__` symbol labels: Mach-O prepends an + underscore to C identifiers, ELF/PE(x64) do not. Used via string-literal + concatenation: __asm__(NIM_ASM_PREFIX "write"). */ +#ifdef __APPLE__ +#define NIM_ASM_PREFIX "_" +#else +#define NIM_ASM_PREFIX "" +#endif + typedef unsigned char NC8; typedef float NF32; diff --git a/src/lengc/gentypes.nim b/src/lengc/gentypes.nim index 756bdf210..ae592a4a8 100644 --- a/src/lengc/gentypes.nim +++ b/src/lengc/gentypes.nim @@ -433,7 +433,15 @@ proc genProcType(c: var GeneratedCode; n: var Cursor; name = ""; isConst = false proc mangleSym(c: var GeneratedCode; s: SymId): string = let x = c.m.getDeclOrNil(s) if x != nil and x.extern != StrId(0): - result = c.m.pool.strings[x.extern] + if x.kind == ProcY and x.bareImport: + # A bare-importc proc keeps its MANGLED C identifier; its prototype + # carries an `__asm__` label binding it to the real symbol (see + # `genProcDecl`). Using the libc identifier here would collide with a + # header prototype whenever a splice moves the reference into a module + # that includes that header. + result = mangleToC(c.m.pool.syms[s]) + else: + result = c.m.pool.strings[x.extern] else: result = mangleToC(c.m.pool.syms[s]) diff --git a/src/lengc/nifmodules.nim b/src/lengc/nifmodules.nim index 38f490def..0cf52b4e8 100644 --- a/src/lengc/nifmodules.nim +++ b/src/lengc/nifmodules.nim @@ -33,6 +33,12 @@ type kind*: LengSym extern*: StrId ## importc/exportc name, cached (frequently queried) isImport*: bool ## true for importc/importcpp, false for exportc-only + bareImport*: bool ## `importc` with neither `header` nor `nodecl`: the + ## C backend declares it itself, under its MANGLED + ## name with an `__asm__` label, so the declaration + ## can never collide with a header prototype for the + ## same libc identifier in the same TU (splices move + ## such references into arbitrary modules) NifProgram = object mods: Table[string, ForeignModule] ## module suffix -> lazily-opened module @@ -93,9 +99,12 @@ proc externName*(s: SymId; n: Cursor): StrId = result = p.strings.getOrIncl(base) proc extractExtern(c: var MainModule; n: var Cursor; pragmasAt: int; - isImport: var bool): StrId = + isImport: var bool; bareImport: var bool): StrId = result = StrId(0) isImport = false + bareImport = false + var sawImportC = false + var sawHeaderish = false n.into: # enter the toplevel (type/proc/var/…) if n.kind != SymbolDef: raiseAssert "Expected SymbolDef after toplevel declaration" @@ -110,6 +119,10 @@ proc extractExtern(c: var MainModule; n: var Cursor; pragmasAt: int; result = externName(symId, n) if pk in {ImportcP, ImportcppP}: isImport = true + if pk == ImportcP: + sawImportC = true + elif pk in {HeaderP, NodeclP}: + sawHeaderish = true skip n elif n.kind == DotToken: discard "ok" @@ -117,6 +130,7 @@ proc extractExtern(c: var MainModule; n: var Cursor; pragmasAt: int; raiseAssert "pragmas not at the correct position" while n.hasMore: skip n + bareImport = sawImportC and not sawHeaderish proc registerTypeBody(c: var MainModule; declPos: Cursor) = ## Map a `(type …)` decl's body position to the decl, so `tracebackTypeC` can @@ -138,19 +152,20 @@ proc getDeclOrNil*(c: var MainModule; s: SymId): ptr Definition = let sk = pos.symKind var extern = StrId(0) var isImport = false + var bareImport = false var n = pos case sk of TypeY: c.types.add pos registerTypeBody(c, pos) - extern = extractExtern(c, n, 1, isImport) + extern = extractExtern(c, n, 1, isImport, bareImport) of ProcY: - extern = extractExtern(c, n, 3, isImport) + extern = extractExtern(c, n, 3, isImport, bareImport) of VarY, ConstY, GvarY, TvarY: - extern = extractExtern(c, n, 1, isImport) + extern = extractExtern(c, n, 1, isImport, bareImport) else: discard c.defs[s] = Definition(pos: pos, kind: sk, extern: extern, - isImport: isImport) + isImport: isImport, bareImport: bareImport) c.requestedForeignSyms.add pos else: raiseAssert "Expected SymbolDef after toplevel declaration" @@ -178,8 +193,10 @@ proc processToplevelDecl(c: var MainModule; n: var Cursor; kind: LengSym; let decl = n let s = firstChild(decl).symId var isImport = false - let extern = extractExtern(c, n, pragmasAt, isImport) - c.defs[s] = Definition(pos: decl, kind: kind, extern: extern, isImport: isImport) + var bareImport = false + let extern = extractExtern(c, n, pragmasAt, isImport, bareImport) + c.defs[s] = Definition(pos: decl, kind: kind, extern: extern, + isImport: isImport, bareImport: bareImport) proc detectToplevelDecls(c: var MainModule) = var n = cursorAt(c.src, 0) diff --git a/src/lengc/shoggoth/cse.nim b/src/lengc/shoggoth/cse.nim index 9faa21a9e..808d1bcf0 100644 --- a/src/lengc/shoggoth/cse.nim +++ b/src/lengc/shoggoth/cse.nim @@ -1050,7 +1050,7 @@ proc readSummary(n: var Cursor; outSummary: var FunctionSummary) = if not sawResult: outSummary.resultCls = uint32(outSummary.params.len) -proc readSummaryPragma(pragmas: Cursor; outSummary: var FunctionSummary): bool = +proc readSummaryPragma*(pragmas: Cursor; outSummary: var FunctionSummary): bool = if pragmas.kind != TagLit: return false var found = false var p = pragmas diff --git a/src/lengc/shoggoth/intermodinliner.nim b/src/lengc/shoggoth/intermodinliner.nim index 4db81e2b8..49a2c71b6 100644 --- a/src/lengc/shoggoth/intermodinliner.nim +++ b/src/lengc/shoggoth/intermodinliner.nim @@ -9,10 +9,12 @@ ## Inter-module inliner for generated NIFC. ## -## Hexer's `intramodinliner` annotates each `.inline` proc's pragma with a -## threshold followed by per-parameter weights (see `intramodinliner`'s -## `computeInlineInfo`). This pass consumes those annotations and splices -## qualifying calls at NIFC level — both same-module and cross-module: +## The inlining policy lives in `intramodinliner.computeInlineInfo` and is +## derived from each proc's body at module-load time (tiny bodies always +## inline, `.noinline` never, bigger bodies go through the per-call-site +## weighted-score heuristic; the `.inline` annotation is ignored). This pass +## applies that policy and splices qualifying calls at NIFC level — both +## same-module and cross-module: ## cross-module bodies are taken from the callee module's post-DCE `.c.nif`, ## picked up via `intramodinliner`'s lazy foreign-module loader (`xnifDir` plus ## a one-level-up search, so the main module's copy inside @@ -113,8 +115,9 @@ proc runInterModuleInliner*(buf: var TokenBuf; suffix: string; ## `xnifDir` is set — `lookupBody` resolves the callee's module via the ## symbol name (`extractModule`) and lazy-loads the foreign `.c.nif`. ## - ## There is no size cap on the callee: `.inline` means the programmer asked - ## for it, so it is honoured whatever the body costs. + ## The size-driven policy (`computeInlineInfo`) bounds what gets spliced: + ## the bodies measured here are the post-flattening `.c.nif` bodies, i.e. + ## exactly what a splice would copy. var ctx = initInlinerCtx(suffix, addr buf, xnifDir = xnifDir, maxDepth = 4, @@ -127,6 +130,7 @@ proc runInterModuleInliner*(buf: var TokenBuf; suffix: string; trIntra(ctx, dest, n) result = dest.len != originalLen buf = ensureMove(dest) + when defined(inlinerStats): dumpInlinerStats(suffix) # ---- self-tests ---------------------------------------------------------- diff --git a/src/lengc/shoggoth/rules/arith.rewrite.nif b/src/lengc/shoggoth/rules/arith.rewrite.nif index e88801987..7539fc1ac 100644 --- a/src/lengc/shoggoth/rules/arith.rewrite.nif +++ b/src/lengc/shoggoth/rules/arith.rewrite.nif @@ -14,4 +14,10 @@ (rule (IF (or (false) X)) (DO X)) (rule (IF (not (not X))) (DO X)) (rule (IF (deref (addr X))) (DO X)) + (rule (IF (eq (nil) (nil))) (DO (true))) + (rule (IF (neq (nil) (nil))) (DO (false))) + (rule (IF (not (true))) (DO (false))) + (rule (IF (not (false))) (DO (true))) + (rule (IF (and (false) (pure X))) (DO (false))) + (rule (IF (or (true) (pure X))) (DO (true))) ) diff --git a/src/lib/nifcore.nim b/src/lib/nifcore.nim index 90865a19a..ca2da363a 100644 --- a/src/lib/nifcore.nim +++ b/src/lib/nifcore.nim @@ -357,7 +357,13 @@ type savedRem: int bodyLen: int -template decRcAndFree(owner: CursorOwner) = +proc decRcAndFree(owner: CursorOwner) = + ## A proc, NOT a template and deliberately NOT `.inline`: this is the cold + ## free path behind every `=destroy`/`=dup` hook. As a template it was + ## expanded into the hooks' bodies, so the (correctly) `.inline` hooks + ## dragged the whole ORC free machinery into every splice site — the single + ## largest source of backend IR blowup. As an out-of-line proc the hooks + ## splice as the few tokens they read as: a nil test and a call. dec owner.rc if owner.rc == 0: if owner.data != nil: dealloc(owner.data) diff --git a/tests/nimony/ffi/tunion.nim b/tests/nimony/ffi/tunion.nim deleted file mode 100644 index 40e4985c6..000000000 --- a/tests/nimony/ffi/tunion.nim +++ /dev/null @@ -1,7 +0,0 @@ -type - Foo {.union.} = object - x: int - c: char - -var x: Foo -x.c = 'a' diff --git a/tests/nimony/ffi/tunion.nim.c b/tests/nimony/ffi/tunion.nim.c deleted file mode 100644 index 6ecc7b42d..000000000 --- a/tests/nimony/ffi/tunion.nim.c +++ /dev/null @@ -1,450 +0,0 @@ -#define NIM_INTBITS 64 -/* GENERATED CODE. DO NOT EDIT. */ - -#ifdef __cplusplus -# if __cplusplus >= 201103L -# /* nullptr is more type safe (less implicit conversions than 0) */ -# define NIM_NIL nullptr -# else -# // both `((void*)0)` and `NULL` would cause codegen to emit -# // error: assigning to 'Foo *' from incompatible type 'void *' -# // but codegen could be fixed if need. See also potential caveat regarding -# // NULL. -# // However, `0` causes other issues, see #13798 -# define NIM_NIL 0 -# endif -#else -# include -# define NIM_NIL NULL -#endif - -#ifdef __cplusplus -#define NB8 bool -#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901) -// see #13798: to avoid conflicts for code emitting `#include ` -#define NB8 _Bool -#else -typedef unsigned char NB8; // best effort -#endif - -typedef unsigned char NC8; - -typedef float NF32; -typedef double NF64; -#if defined(__BORLANDC__) || defined(_MSC_VER) -typedef signed char NI8; -typedef signed short int NI16; -typedef signed int NI32; -typedef __int64 NI64; -/* XXX: Float128? */ -typedef unsigned char NU8; -typedef unsigned short int NU16; -typedef unsigned int NU32; -typedef unsigned __int64 NU64; -#elif defined(HAVE_STDINT_H) -#ifndef USE_NIM_NAMESPACE -# include -#endif -typedef int8_t NI8; -typedef int16_t NI16; -typedef int32_t NI32; -typedef int64_t NI64; -typedef uint8_t NU8; -typedef uint16_t NU16; -typedef uint32_t NU32; -typedef uint64_t NU64; -#elif defined(HAVE_CSTDINT) -#ifndef USE_NIM_NAMESPACE -# include -#endif -typedef std::int8_t NI8; -typedef std::int16_t NI16; -typedef std::int32_t NI32; -typedef std::int64_t NI64; -typedef std::uint8_t NU8; -typedef std::uint16_t NU16; -typedef std::uint32_t NU32; -typedef std::uint64_t NU64; -#else -/* Unknown compiler/version, do our best */ -#ifdef __INT8_TYPE__ -typedef __INT8_TYPE__ NI8; -#else -typedef signed char NI8; -#endif -#ifdef __INT16_TYPE__ -typedef __INT16_TYPE__ NI16; -#else -typedef signed short int NI16; -#endif -#ifdef __INT32_TYPE__ -typedef __INT32_TYPE__ NI32; -#else -typedef signed int NI32; -#endif -#ifdef __INT64_TYPE__ -typedef __INT64_TYPE__ NI64; -#else -typedef long long int NI64; -#endif -/* XXX: Float128? */ -#ifdef __UINT8_TYPE__ -typedef __UINT8_TYPE__ NU8; -#else -typedef unsigned char NU8; -#endif -#ifdef __UINT16_TYPE__ -typedef __UINT16_TYPE__ NU16; -#else -typedef unsigned short int NU16; -#endif -#ifdef __UINT32_TYPE__ -typedef __UINT32_TYPE__ NU32; -#else -typedef unsigned int NU32; -#endif -#ifdef __UINT64_TYPE__ -typedef __UINT64_TYPE__ NU64; -#else -typedef unsigned long long int NU64; -#endif -#endif - -#ifdef NIM_INTBITS -# if NIM_INTBITS == 64 -typedef NI64 NI; -typedef NU64 NU; -# elif NIM_INTBITS == 32 -typedef NI32 NI; -typedef NU32 NU; -# elif NIM_INTBITS == 16 -typedef NI16 NI; -typedef NU16 NU; -# elif NIM_INTBITS == 8 -typedef NI8 NI; -typedef NU8 NU; -# else -# error "invalid bit width for int" -# endif -#endif - -#define NIM_TRUE true -#define NIM_FALSE false - -#define _GNU_SOURCE - -// Include math.h to use `NAN` that should be defined in C compilers supports C99. -#include - -// Define NAN in case math.h doesn't define it. -// NAN definition copied from math.h included in the Windows SDK version 10.0.14393.0 -#ifndef NAN -# ifndef _HUGE_ENUF -# define _HUGE_ENUF 1e+300 // _HUGE_ENUF*_HUGE_ENUF must overflow -# endif -# define NAN_INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF)) -# define NAN ((float)(NAN_INFINITY * 0.0F)) -#endif - -#ifndef INF -# ifdef INFINITY -# define INF INFINITY -# elif defined(HUGE_VAL) -# define INF HUGE_VAL -# elif defined(_MSC_VER) -# include -# define INF (DBL_MAX+DBL_MAX) -# else -# define INF (1.0 / 0.0) -# endif -#endif - -#if defined(__GNUC__) || defined(_MSC_VER) -# define IL64(x) x##LL -#else /* works only without LL */ -# define IL64(x) ((NI64)x) -#endif - - -/* ------------ ignore typical warnings in Nim-generated files ------------- */ -#if defined(__GNUC__) || defined(__clang__) -# pragma GCC diagnostic ignored "-Wswitch-bool" -# pragma GCC diagnostic ignored "-Wformat" -# pragma GCC diagnostic ignored "-Wpointer-sign" -# if defined(__clang__) -# pragma GCC diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers" -# else -# pragma GCC diagnostic ignored "-Wdiscarded-qualifiers" -# endif -#endif - - - -/* ------------------------------------------------------------------- */ -#ifdef __cplusplus -# define NIM_EXTERNC extern "C" -#else -# define NIM_EXTERNC -#endif - -#if defined(WIN32) || defined(_WIN32) /* only Windows has this mess... */ -# define N_LIB_PRIVATE -# define N_CDECL(rettype, name) rettype __cdecl name -# define N_STDCALL(rettype, name) rettype __stdcall name -# define N_SYSCALL(rettype, name) rettype __syscall name -# define N_FASTCALL(rettype, name) rettype __fastcall name -# define N_THISCALL(rettype, name) rettype __thiscall name -# define N_SAFECALL(rettype, name) rettype __stdcall name -/* function pointers with calling convention: */ -# define N_CDECL_PTR(rettype, name) rettype (__cdecl *name) -# define N_STDCALL_PTR(rettype, name) rettype (__stdcall *name) -# define N_SYSCALL_PTR(rettype, name) rettype (__syscall *name) -# define N_FASTCALL_PTR(rettype, name) rettype (__fastcall *name) -# define N_THISCALL_PTR(rettype, name) rettype (__thiscall *name) -# define N_SAFECALL_PTR(rettype, name) rettype (__stdcall *name) - -# ifdef __EMSCRIPTEN__ -# define N_LIB_EXPORT NIM_EXTERNC __declspec(dllexport) __attribute__((used)) -# define N_LIB_EXPORT_VAR __declspec(dllexport) __attribute__((used)) -# else -# define N_LIB_EXPORT NIM_EXTERNC __declspec(dllexport) -# define N_LIB_EXPORT_VAR __declspec(dllexport) -# endif -# define N_LIB_IMPORT extern __declspec(dllimport) -#else -# define N_LIB_PRIVATE __attribute__((visibility("hidden"))) -# if defined(__GNUC__) -# define N_CDECL(rettype, name) rettype name -# define N_STDCALL(rettype, name) rettype name -# define N_SYSCALL(rettype, name) rettype name -# define N_FASTCALL(rettype, name) __attribute__((fastcall)) rettype name -# define N_SAFECALL(rettype, name) rettype name -/* function pointers with calling convention: */ -# define N_CDECL_PTR(rettype, name) rettype (*name) -# define N_STDCALL_PTR(rettype, name) rettype (*name) -# define N_SYSCALL_PTR(rettype, name) rettype (*name) -# define N_FASTCALL_PTR(rettype, name) __attribute__((fastcall)) rettype (*name) -# define N_SAFECALL_PTR(rettype, name) rettype (*name) -# else -# define N_CDECL(rettype, name) rettype name -# define N_STDCALL(rettype, name) rettype name -# define N_SYSCALL(rettype, name) rettype name -# define N_FASTCALL(rettype, name) rettype name -# define N_SAFECALL(rettype, name) rettype name -/* function pointers with calling convention: */ -# define N_CDECL_PTR(rettype, name) rettype (*name) -# define N_STDCALL_PTR(rettype, name) rettype (*name) -# define N_SYSCALL_PTR(rettype, name) rettype (*name) -# define N_FASTCALL_PTR(rettype, name) rettype (*name) -# define N_SAFECALL_PTR(rettype, name) rettype (*name) -# endif -# ifdef __EMSCRIPTEN__ -# define N_LIB_EXPORT NIM_EXTERNC __attribute__((visibility("default"), used)) -# define N_LIB_EXPORT_VAR __attribute__((visibility("default"), used)) -# else -# define N_LIB_EXPORT NIM_EXTERNC __attribute__((visibility("default"))) -# define N_LIB_EXPORT_VAR __attribute__((visibility("default"))) -# endif -# define N_LIB_IMPORT extern -#endif - -#if defined(__BORLANDC__) || defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) -/* these compilers have a fastcall so use it: */ -# define N_NIMCALL(rettype, name) rettype __fastcall name -# define N_NIMCALL_PTR(rettype, name) rettype (__fastcall *name) -#else -# define N_NIMCALL(rettype, name) rettype name /* no modifier */ -# define N_NIMCALL_PTR(rettype, name) rettype (*name) -#endif - -#define N_NOCONV(rettype, name) rettype name -/* specify no calling convention */ -#define N_NOCONV_PTR(rettype, name) rettype (*name) - -/* calling convention mess ----------------------------------------------- */ -#if defined(__GNUC__) || defined(__TINYC__) - /* these should support C99's inline */ -# define N_INLINE(rettype, name) inline rettype name -#elif defined(__BORLANDC__) || defined(_MSC_VER) -/* Borland's compiler is really STRANGE here; note that the __fastcall - keyword cannot be before the return type, but __inline cannot be after - the return type, so we do not handle this mess in the code generator - but rather here. */ -# define N_INLINE(rettype, name) __inline rettype name -#else /* others are less picky: */ -# define N_INLINE(rettype, name) rettype __inline name -#endif - -#define N_INLINE_PTR(rettype, name) rettype (*name) - -#if defined(__GNUC__) || defined(__ICC__) -# define N_NOINLINE __attribute__((__noinline__)) -#elif defined(_MSC_VER) -# define N_NOINLINE __declspec(noinline) -#else -# define N_NOINLINE -#endif - -#define N_NOINLINE_PTR(rettype, name) rettype (*name) - -#if defined(_MSC_VER) -# define NIM_ALIGN(x) __declspec(align(x)) -# define NIM_ALIGNOF(x) __alignof(x) -#else -# define NIM_ALIGN(x) __attribute__((aligned(x))) -# define NIM_ALIGNOF(x) __alignof__(x) -#endif - -#include - - -/* - NIM_THREADVAR declaration based on - https://stackoverflow.com/questions/18298280/how-to-declare-a-variable-as-thread-local-portably -*/ -#if defined _WIN32 -# if defined _MSC_VER || defined __BORLANDC__ -# define NIM_THREADVAR __declspec(thread) -# else -# define NIM_THREADVAR __thread -# endif -#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112 && !defined __STDC_NO_THREADS__ -# define NIM_THREADVAR _Thread_local -#elif defined _WIN32 && ( \ - defined _MSC_VER || \ - defined __ICL || \ - defined __BORLANDC__ ) -# define NIM_THREADVAR __declspec(thread) -#elif defined(__TINYC__) || defined(__GENODE__) -# define NIM_THREADVAR -/* note that ICC (linux) and Clang are covered by __GNUC__ */ -#elif defined __GNUC__ || \ - defined __SUNPRO_C || \ - defined __xlC__ -# define NIM_THREADVAR __thread -#else -# error "Cannot define NIM_THREADVAR" -#endif - -/* define NIM_STATIC_ASSERT */ -#if defined(__cplusplus) -#define NIM_STATIC_ASSERT(x, msg) static_assert((x), msg) -#else -#define NIM_STATIC_ASSERT(x, msg) _Static_assert((x), msg) -#endif - -// Test to see if Nim and the C compiler agree on the size of a pointer. -NIM_STATIC_ASSERT(sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8, "Pointer size mismatch between Nim and C/C++ backend. You probably need to setup the backend compiler for target CPU."); - -N_INLINE(NB8, _Qlengc_div_sll_overflow)(long long int a, long long int b, long long int *res) { - if (b == 0) { - *res = 0; - return NIM_TRUE; - } - if (a == (long long int)(((unsigned long long int)1) << (sizeof(long long int) * 8 - 1)) && b == -1) { - *res = a; - return NIM_TRUE; - } - *res = a / b; - return NIM_FALSE; -} - -N_INLINE(NB8, _Qlengc_div_sl_overflow)(long int a, long int b, long int *res) { - if (b == 0) { - *res = 0; - return NIM_TRUE; - } - if (a == (long int)(((unsigned long int)1) << (sizeof(long int) * 8 - 1)) && b == -1) { - *res = a; - return NIM_TRUE; - } - *res = a / b; - return NIM_FALSE; -} - -N_INLINE(NB8, _Qlengc_div_ull_overflow)(unsigned long long int a, unsigned long long int b, unsigned long long int *res) { - if (b == 0) { - *res = 0; - return NIM_TRUE; /* Overflow: division by zero */ - } - *res = a / b; - return NIM_FALSE; -} - -N_INLINE(NB8, _Qlengc_div_ul_overflow)(unsigned long int a, unsigned long int b, unsigned long int *res) { - if (b == 0) { - *res = 0; - return NIM_TRUE; - } - *res = a / b; - return NIM_FALSE; -} - -N_INLINE(NB8, _Qlengc_mod_sll_overflow)(long long int a, long long int b, long long int *res) { - if (b == 0) { - *res = 0; - return NIM_TRUE; - } - if (a == (long long int)(((unsigned long long int)1) << (sizeof(long long int) * 8 - 1)) && b == -1) { - *res = 0; - return NIM_TRUE; - } - *res = a % b; - return NIM_FALSE; -} - -N_INLINE(NB8, _Qlengc_mod_sl_overflow)(long int a, long int b, long int *res) { - if (b == 0) { - *res = 0; - return NIM_TRUE; - } - if (a == (long int)(((unsigned long int)1) << (sizeof(long int) * 8 - 1)) && b == -1) { - *res = 0; - return NIM_TRUE; - } - *res = a % b; - return NIM_FALSE; -} - -N_INLINE(NB8, _Qlengc_mod_ull_overflow)(unsigned long long int a, unsigned long long int b, unsigned long long int *res) { - if (b == 0) { - *res = 0; - return NIM_TRUE; - } - *res = a % b; - return NIM_FALSE; -} - -N_INLINE(NB8, _Qlengc_mod_ul_overflow)(unsigned long int a, unsigned long int b, unsigned long int *res) { - if (b == 0) { - *res = 0; - return NIM_TRUE; - } - *res = a % b; - return NIM_FALSE; -} -NIM_THREADVAR NB8 LENGC_ERR_; -typedef union Foo_0_tun261nex{ - NI64 x_0; - NC8 c_0;} -Foo_0_tun261nex; -extern void X60Qini_0_sysvq0asl(void); -extern void nimFlushStdStreams(void); -Foo_0_tun261nex x_0_tun261nex; -NB8 X60QiniGuard_0_tun261nex; -NI32 cmdCount; -NC8** cmdLine; -NC8** nimEnviron; -void X60Qini_0_tun261nex(void){ - if (X60QiniGuard_0_tun261nex){ - return;} - X60QiniGuard_0_tun261nex = NIM_TRUE; - X60Qini_0_sysvq0asl(); - x_0_tun261nex.c_0 = (NC8)'a';} -NI32 main(NI32 X60Qargc_0_tun261nex, char** X60Qargv_0_tun261nex, char** X60Qenvp_0_tun261nex){ - cmdCount = X60Qargc_0_tun261nex; - cmdLine = ((NC8**)X60Qargv_0_tun261nex); - nimEnviron = ((NC8**)X60Qenvp_0_tun261nex); - X60Qini_0_tun261nex(); - nimFlushStdStreams(); - return IL64(0);}