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
31 changes: 29 additions & 2 deletions compiler/liftdestructors.nim
Original file line number Diff line number Diff line change
Expand Up @@ -620,11 +620,34 @@ proc checkSelfAssignment(c: var TLiftCtx; t: PType; body, x, y: PNode) =
cond.typ = getSysType(c.g, c.info, tyBool)
body.add genIf(c, cond, newTreeI(nkReturnStmt, c.info, newNodeI(nkEmpty, c.info)))

proc genBulkCopySeq(c: var TLiftCtx; t: PType; body, x, y: PNode) =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why generate this in the compiler? can't it be library code?

Copy link
Copy Markdown
Member

@Araq Araq Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The =copy hook for seq is still special magic in Nim 2. Mostly caused by the fact that people expect seq to work within Nim's VM which doesn't support the required cast/alloc/etc stuff. There is also const s = @["abc"] which is valid Nim code (makes no sense here but comes up for Table in const sections).

## Generates a call to nimCopySeqPayload for bulk memcpy of seq data.
let elemType = t.elementType
let sym = magicsys.getCompilerProc(c.g, "nimCopySeqPayload")
if sym == nil:
localError(c.g.config, c.info, "system module needs: nimCopySeqPayload")
return
var sizeOf = genBuiltin(c, mSizeOf, "sizeof", newNodeIT(nkType, c.info, elemType))
sizeOf.typ = getSysType(c.g, c.info, tyInt)
var alignOf = genBuiltin(c, mAlignOf, "alignof", newNodeIT(nkType, c.info, elemType))
alignOf.typ = getSysType(c.g, c.info, tyInt)
let call = newNodeI(nkCall, c.info)
call.add newSymNode(sym)
call.add newTreeIT(nkAddr, c.info, makePtrType(c.fn, x.typ, c.idgen), x)
call.add newTreeIT(nkAddr, c.info, makePtrType(c.fn, y.typ, c.idgen), y)
call.add sizeOf
call.add alignOf
call.typ = sym.typ.returnType
body.add call

proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
case c.kind
of attachedDup:
body.add setLenSeqCall(c, t, x, y)
forallElements(c, t, body, x, y)
if supportsCopyMem(t.elementType):
genBulkCopySeq(c, t, body, x, y)
else:
forallElements(c, t, body, x, y)
of attachedAsgn, attachedDeepCopy:
# we generate:
# if x.p == y.p:
Expand All @@ -633,9 +656,13 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
# var i = 0
# while i < y.len: dest[i] = y[i]; inc(i)
# This is usually more efficient than a destroy/create pair.
# For trivially copyable types, use bulk copyMem instead of element loop.
checkSelfAssignment(c, t, body, x, y)
body.add setLenSeqCall(c, t, x, y)
forallElements(c, t, body, x, y)
if supportsCopyMem(t.elementType):
genBulkCopySeq(c, t, body, x, y)
else:
forallElements(c, t, body, x, y)
Comment on lines 643 to +665
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New bulk-copy path for seq assignment/copy is performance-critical and changes generated code for ORC/ARC. Please add a regression test that asserts this path is exercised for a trivially-copyable element type (e.g., seq[byte]), ideally by checking --expandArc/--expandOrc (or other compiler output) contains a call to nimCopySeqPayload, so the optimization doesn’t silently regress again.

Copilot uses AI. Check for mistakes.
of attachedSink:
let moveCall = genBuiltin(c, mMove, "move", x)
moveCall.add y
Expand Down
5 changes: 1 addition & 4 deletions compiler/semmagic.nim
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,7 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym)
of "stripGenericParams":
result = uninstantiate(operand).toNode(traitCall.info)
of "supportsCopyMem":
let t = operand.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred})
let complexObj = containsGarbageCollectedRef(t) or
hasDestructor(t)
result = newIntNodeT(toInt128(ord(not complexObj)), traitCall, c.idgen, c.graph)
result = newIntNodeT(toInt128(ord(supportsCopyMem(operand))), traitCall, c.idgen, c.graph)
of "canFormCycles":
result = newIntNodeT(toInt128(ord(types.canFormAcycle(c.graph, operand))), traitCall, c.idgen, c.graph)
of "hasDefaultValue":
Expand Down
4 changes: 4 additions & 0 deletions compiler/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1779,3 +1779,7 @@ proc reduceToBase*(f: PType): PType =
result = f.elementType
else:
result = f

proc supportsCopyMem*(t: PType): bool =
let t = t.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred})
result = not containsGarbageCollectedRef(t) and not hasDestructor(t)
10 changes: 10 additions & 0 deletions lib/system/seqs_v2.nim
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,16 @@ proc newSeq[T](s: var seq[T], len: Natural) =
proc sameSeqPayload(x: pointer, y: pointer): bool {.compilerRtl, inl.} =
result = cast[ptr NimRawSeq](x)[].p == cast[ptr NimRawSeq](y)[].p

proc nimCopySeqPayload(dest: pointer, src: pointer, elemSize: int, elemAlign: int) {.compilerRtl, inline.} =
## Bulk-copies the payload data from src seq to dest seq using copyMem.
## Only valid for trivially copyable element types (no GC refs, no destructors).
## Caller must have already ensured dest has the correct length and capacity
## (e.g. via setLen).
let d = cast[ptr NimRawSeq](dest)
let s = cast[ptr NimRawSeq](src)
if s.len > 0:
let headerSize = align(sizeof(NimSeqPayloadBase), elemAlign)
copyMem(d.p +! headerSize, s.p +! headerSize, s.len * elemSize)

func capacity*[T](self: seq[T]): int {.inline.} =
## Returns the current capacity of the seq.
Expand Down
Loading