diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index f0a5acc78cfd..eecb850d52eb 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -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) = + ## 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: @@ -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) of attachedSink: let moveCall = genBuiltin(c, mMove, "move", x) moveCall.add y diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 87e085d4fd6e..5e4388d3c6c0 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -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": diff --git a/compiler/types.nim b/compiler/types.nim index e18f97ff363c..62831624b930 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -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) diff --git a/lib/system/seqs_v2.nim b/lib/system/seqs_v2.nim index 154b4434602f..1411375407e7 100644 --- a/lib/system/seqs_v2.nim +++ b/lib/system/seqs_v2.nim @@ -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.