Skip to content
Open
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
23 changes: 23 additions & 0 deletions src/hexer/coro_transform.nim
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ const
## tuple's env slot so iter values have the same `(ref RootObj)`
## shape as closure procs.
EnvParamName* = "`this.0"
ClosureEnvParamName* = "`ep.0"
## The env param appended to a lowered closure signature (distinct from the
## coroutine's `this.0` env above). Lives here — like `RootObjName` and the
## wrapper-signature shape — so lambdalifting's pass-2 lowering and any other
## pass that must emit the identical env slot (e.g. a cross-module foreign-decl
## canonicalizer) stay in lock-step off one definition.
FnFieldName* = "fn.0"
EnvFieldName* = "env.0"
CallerFieldName* = "caller.0"
Expand All @@ -69,6 +75,23 @@ const
AllocFrameProcName* = "allocFrame.0." & SystemModuleSuffix
DeallocFrameProcName* = "deallocFrame.0." & SystemModuleSuffix

proc addClosureEnvParam*(dest: var TokenBuf; info: NifLineInfo; envTyp: SymId) =
## Emit the trailing env `(param)` of a lowered closure signature. `envTyp == 0`
## uses the generic `(ref RootObj)` slot shared with iter values; a concrete env
## type uses a `(ptr)` (NIFC needs the pointer type here, with a cast in the body).
dest.copyIntoKind ParamU, info:
dest.addSymDef pool.syms.getOrIncl(ClosureEnvParamName), info
dest.addDotToken() # no export marker
dest.addDotToken() # no pragmas
if envTyp == SymId(0):
dest.copyIntoKind RefT, info:
dest.addSymUse pool.syms.getOrIncl(BareRootObjName), info
else:
# to keep NIFC's type system happy we need a ptr type here
# and then a cast in the body!
dest.copyIntoKind PointerT, info: discard
dest.addDotToken() # no default value

type
EnvField* = object
objType*: SymId
Expand Down
25 changes: 23 additions & 2 deletions src/hexer/duplifier.nim
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,26 @@ proc callWasMoved(c: var Context; sym: SymId; info: NifLineInfo; typ: Cursor) =
copyIntoKind c.dest, HaddrX, info:
copyIntoSymUse c.dest, sym, info

proc writesThroughCursorField(le: Cursor): bool =
## True when the assignment target `le` is a write through a `{.cursor.}`
## object field, e.g. `obj.cursorField = x`. Such a field is a non-owning
## alias: the store must be a raw bitcopy (no `=destroy` of the old value,
## no `=dup`/`=copy` of the new), matching the constructor-side handling in
## `isCursorField` / trObjConstr and the lifter's `unravelObjField`. Without
## this, a captured `{.cursor.}` local hoisted into a closure environment
## (see lambdalifting) would get `=dup`'d into its env field, re-forming the
## exact ref cycle the cursor was written to break.
if le.exprKind != DotX: return false
var f = le
inc f # into the dot: object operand
skip f # skip the object operand -> field symbol
if f.kind != Symbol: return false
let res = tryLoadSym(f.symId)
Comment thread
tokyovigilante marked this conversation as resolved.
if res.status != LacksNothing: return false
let local = asLocal(res.decl)
if local.kind notin {FldY, GfldY}: return false
result = hasPragma(local.pragmas, CursorP)

proc trAsgn(c: var Context; n: var Cursor) =
#[
`x = f()` is turned into `let tmp = f(); =destroy(x); x =bitcopy tmp` #`f()` can read `x`
Expand Down Expand Up @@ -498,8 +518,9 @@ proc trAsgn(c: var Context; n: var Cursor) =
# over-decrement the rc on a value the cursor never claimed ownership of
# (and matching =destroy at end of scope was suppressed for the same
# reason). Treat cursor lhs's like the no-destructor case — raw bitcopy.
let lhsIsCursor = le.kind == Symbol and
c.typeCache.getLocalInfo(le.symId).kind == CursorY
let lhsIsCursor = (le.kind == Symbol and
c.typeCache.getLocalInfo(le.symId).kind == CursorY) or
writesThroughCursorField(le)
if destructor == NoSymId or lhsIsCursor:
# the type has no destructor, there is nothing interesting to do:
trSons c, n, DontCare
Expand Down
32 changes: 27 additions & 5 deletions src/hexer/iterinliner.nim
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,35 @@ proc copyWithMapping(dest: var TokenBuf; c: var Cursor; mapping: Table[SymId, Sy
## Used by `inlineLoopBody` to buffer a for-stmt before handing it to
## `transformForStmt`, so that nested for-stmts inside are inlined fresh
## (with distinct labels) on every yield expansion of the outer iterator.
##
## Field-identity positions — `(dot obj FIELD …)` selectors and `(kv
## FIELD value)` keys — are copied verbatim, NOT substituted: sem's
## `name.N` numbering is per module, so a foreign type's field sym can
## be the same interned string as a local this mapping renames (e.g.
## field `writes.0` of another module's object vs. the first local
## `writes` in this one).
case c.kind
of TagLit:
dest.addParLe(c.cursorTagId, c.info)
c.into:
while c.hasMore:
copyWithMapping(dest, c, mapping)
dest.addParRi(c.endInfo)
if c.exprKind in {DotX, DdotX}:
dest.addParLe(c.cursorTagId, c.info)
c.into:
copyWithMapping(dest, c, mapping) # object expression
while c.hasMore:
dest.takeTree c # field selector + optional depth/access token
dest.addParRi(c.endInfo)
elif c.substructureKind == KvU:
dest.addParLe(c.cursorTagId, c.info)
c.into:
dest.takeTree c # field name
while c.hasMore:
copyWithMapping(dest, c, mapping)
dest.addParRi(c.endInfo)
else:
dest.addParLe(c.cursorTagId, c.info)
c.into:
while c.hasMore:
copyWithMapping(dest, c, mapping)
dest.addParRi(c.endInfo)
of Symbol:
let s = c.symId
if mapping.hasKey(s):
Expand Down
Loading
Loading