Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions src/hastur.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
720 changes: 521 additions & 199 deletions src/hexer/intramodinliner.nim

Large diffs are not rendered by default.

95 changes: 67 additions & 28 deletions src/hexer/lengcgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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:
#
# <try body> # every raise inside became (jmp `exlab.N)
# <finally> # normal path only, see below
# (jmp `exend.N)
# (stmts (lab :`exlab.N) <handler>)
# (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)
Expand All @@ -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
Expand Down
31 changes: 29 additions & 2 deletions src/hexer/lifter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
31 changes: 27 additions & 4 deletions src/lengc/codegen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand All @@ -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 <unistd.h> 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
Expand Down
9 changes: 9 additions & 0 deletions src/lengc/cprelude.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 9 additions & 1 deletion src/lengc/gentypes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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])

Expand Down
31 changes: 24 additions & 7 deletions src/lengc/nifmodules.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand All @@ -110,13 +119,18 @@ 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"
else:
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
Expand All @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/lengc/shoggoth/cse.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading