js: cursor inference to elide nimCopy for safe value aliases (#25948)

This commit is contained in:
Savant
2026-07-03 03:55:58 -04:00
committed by GitHub
parent 985b1125b1
commit c7ea004ca9
2 changed files with 29 additions and 8 deletions

View File

@@ -34,7 +34,7 @@ import
ropes, wordrecg, renderer,
cgmeth, lowerings, sighashes, modulegraphs, lineinfos,
transf, injectdestructors, sourcemap, astmsgs, pushpoppragmas,
mangleutils
mangleutils, varpartitions
import pipelineutils
@@ -1298,14 +1298,16 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) =
xtyp = etySeq
case xtyp
of etySeq:
if x.typ.kind in {tyVar, tyLent} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded:
if x.typ.kind in {tyVar, tyLent} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded or
(x.kind == nkSym and sfCursor in x.sym.flags):
lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
else:
useMagic(p, "nimCopy")
lineF(p, "$1 = nimCopy(null, $2, $3);$n",
[a.rdLoc, b.res, genTypeInfo(p, y.typ)])
of etyObject:
if x.typ.kind in {tyVar, tyLent, tyOpenArray, tyVarargs} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded:
if x.typ.kind in {tyVar, tyLent, tyOpenArray, tyVarargs} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded or
(x.kind == nkSym and sfCursor in x.sym.flags):
lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
else:
useMagic(p, "nimCopy")
@@ -2092,7 +2094,8 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
gen(p, n, a)
case mapType(p, v.typ)
of etyObject, etySeq:
if v.typ.kind in {tyOpenArray, tyVarargs} or needsNoCopy(p, n):
if v.typ.kind in {tyOpenArray, tyVarargs} or needsNoCopy(p, n) or
sfCursor in v.flags:
s = a.res
else:
useMagic(p, "nimCopy")
@@ -2798,6 +2801,11 @@ proc genProc(oldProc: PProc, prc: PSym): Rope =
var transformedBody = transformBody(p.module.graph, p.module.idgen, prc, {})
if sfInjectDestructors in prc.flags:
transformedBody = injectDestructorCalls(p.module.graph, p.module.idgen, prc, transformedBody)
else:
# JS has a GC, so the destructor pass is off; but the cursor (alias) analysis
# is independent of ownership and always memory-safe on a traced target.
# Running it lets last-use `var b = a` aliases skip the deep `nimCopy`.
computeCursors(prc, transformedBody, p.module.graph)
p.nested: genStmt(p, transformedBody)

View File

@@ -677,9 +677,13 @@ proc deps(c: var Partitions; dest, src: PNode) =
else:
let srcid = variableId(c, s)
if srcid >= 0:
if s.kind notin {skResult, skParam} and (
c.s[srcid].aliveEnd < c.s[vid].aliveEnd):
# you cannot borrow from a local that lives shorter than 'vid':
if s.kind notin {skResult, skParam} and
c.s[srcid].aliveEnd < c.s[vid].aliveEnd and
c.g.config.backend != backendJs:
# you cannot borrow from a local that lives shorter than 'vid'.
# On a traced (JS/GC) target the source object stays alive as long
# as the alias references it, so this lifetime rule does not apply;
# value-semantics safety is enforced by `dangerousMutation` instead.
when explainCursors: echo "B not a cursor ", d.sym, " ", c.s[srcid].aliveEnd, " ", c.s[vid].aliveEnd
c.s[vid].flags.incl preventCursor
elif {isReassigned, preventCursor} * c.s[srcid].flags != {}:
@@ -1003,13 +1007,22 @@ proc checkBorrowedLocations*(par: var Partitions; body: PNode; config: ConfigRef
#if par.s[rid].con.kind == isRootOf and dangerousMutation(par.graphs[par.s[rid].con.graphIndex], par.s[i]):
# cannotBorrow(config, s, par.graphs[par.s[rid].con.graphIndex])
proc jsDeepCopied(t: PType): bool =
## On the JS backend `nimCopy` deep-copies these type classes on every
## assignment, so eliding the copy for a safe alias is worthwhile even when
## the type has no C-style destructor.
t.skipTypes({tyGenericInst, tyAlias, tyDistinct, tyVar, tyLent}).kind in
{tyObject, tyTuple, tyArray, tySequence, tyString}
proc computeCursors*(s: PSym; n: PNode; g: ModuleGraph) =
let jsCursors = g.config.backend == backendJs
var par = computeGraphPartitions(s, n, g, {cursorInference})
for i in 0 ..< par.s.len:
let v = addr(par.s[i])
if v.flags * {ownsData, preventCursor, isConditionallyReassigned} == {} and
v.sym.kind notin {skParam, skResult} and
v.sym.flags * {sfThread, sfGlobal} == {} and hasDestructor(v.sym.typ) and
v.sym.flags * {sfThread, sfGlobal} == {} and
(hasDestructor(v.sym.typ) or (jsCursors and jsDeepCopied(v.sym.typ))) and
v.sym.typ.skipTypes({tyGenericInst, tyAlias}).kind != tyOwned and
(getAttachedOp(g, v.sym.typ, attachedAsgn) == nil or
sfError notin getAttachedOp(g, v.sym.typ, attachedAsgn).flags):