From 0a94454bcdd63d4a90f79c248083c6c1cfa8fdf1 Mon Sep 17 00:00:00 2001 From: zerbina <100542850+zerbina@users.noreply.github.com> Date: Sat, 10 Aug 2024 20:15:16 +0000 Subject: [PATCH] mir: lower `cast` with MIR pass Summary ======= When compiling for the C backend, use a MIR pass to lower `cast`s between non-integer/non-pointer types into `nimCopyMem`. Details ======= * the `lowerCast` does the lowering, with the decision whether to use `nimCopyMem` based on the lowered MIR type * handling of `float` and aggregate type casts is removed from `cgen` * `cgen` used type-punning through a union for such casts, which is possible, but not as easy to do with a MIR pass, so `nimCopyMem` (i.e., `memcpy`) is used instead The copy always copies size-of-destination number of bytes. --- compiler/backend/ccgexprs.nim | 34 ++++---------- compiler/mir/mirpasses.nim | 83 +++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 25 deletions(-) diff --git a/compiler/backend/ccgexprs.nim b/compiler/backend/ccgexprs.nim index 0410ff813bb..fa99015cb8a 100644 --- a/compiler/backend/ccgexprs.nim +++ b/compiler/backend/ccgexprs.nim @@ -1189,16 +1189,13 @@ proc genSomeCast(p: BProc, e: CgNode, d: var TLoc) = of cnkCast, cnkConv, cnkHiddenConv: e.operand of cnkCall: e[1] else: unreachable() - # we use whatever C gives us. Except if we have a value-type, we need to go - # through its address: + # we use whatever C gives us var a: TLoc initLocExpr(p, src, a) let etyp = skipTypes(e.typ, abstractRange) let srcTyp = skipTypes(src.typ, abstractRange) - if etyp.kind in ValueTypes and lfIndirect notin a.flags: - putIntoDest(p, d, e, "(*($1*) ($2))" % - [getTypeDesc(p.module, e.typ), addrLoc(p.module, a)], a.storage) - elif etyp.kind == tyProc and etyp.callConv == ccClosure and srcTyp.callConv != ccClosure: + p.config.internalAssert(etyp.kind notin ValueTypes, e.info) + if etyp.kind == tyProc and etyp.callConv == ccClosure and srcTyp.callConv != ccClosure: putIntoDest(p, d, e, "(($1) ($2))" % [getClosureType(p.module, etyp, clHalfWithEnv), rdCharLoc(a)], a.storage) else: @@ -1216,26 +1213,13 @@ proc genSomeCast(p: BProc, e: CgNode, d: var TLoc) = putIntoDest(p, d, e, "(($1) ($2))" % [getTypeDesc(p.module, e.typ), rdCharLoc(a)], a.storage) +proc canon(types: TypeEnv, typ: PType): TypeId = + types.canonical(types[typ]) + proc genCast(p: BProc, e: CgNode, d: var TLoc) = - const ValueTypes = {tyFloat..tyFloat64, tyTuple, tyObject, tyArray} - let - src = e.operand - destt = skipTypes(e.typ, abstractRange) - srct = skipTypes(src.typ, abstractRange) - if destt.kind in ValueTypes or srct.kind in ValueTypes: - # 'cast' and some float type involved? --> use a union. - inc(p.labels) - var lbl = p.labels.rope - var tmp: TLoc - tmp.r = "LOC$1.source" % [lbl] - linefmt(p, cpsLocals, "union { $1 source; $2 dest; } LOC$3;$n", - [getTypeDesc(p.module, src.typ), getTypeDesc(p.module, e.typ), lbl]) - tmp.k = locExpr - tmp.lode = lodeTyp srct - tmp.storage = OnStack - tmp.flags = {} - expr(p, src, tmp) - putIntoDest(p, d, e, "LOC$#.dest" % [lbl], tmp.storage) + let x = e.operand + if canon(p.module.types, e.typ) == canon(p.module.types, x.typ): + expr(p, x, d) # no cast is necessary else: # I prefer the shorter cast version for pointer types -> generate less # C code; plus it's the right thing to do for closures: diff --git a/compiler/mir/mirpasses.nim b/compiler/mir/mirpasses.nim index cbb5ab38bf8..5466b370d7e 100644 --- a/compiler/mir/mirpasses.nim +++ b/compiler/mir/mirpasses.nim @@ -122,6 +122,17 @@ proc overlapsConservative(tree: MirTree, a, b: Path, typA, typB: PType): bool = # use path-based analysis: result = overlaps(tree, a, b) != no +proc genSizeOf(bu: var MirBuilder, env: var MirEnv, typ: TypeId): Value = + let size = env.types.headerFor(typ, Lowered).size(env.types) + if size >= 0: + # known size -> use a literal value + literal(mnkIntLit, env.getOrIncl(size), env.types.sizeType) + else: + # some type with unknown size -> emit a sizeof call + bu.wrapTemp env.types.sizeType: + bu.buildMagicCall mSizeOf, env.types.sizeType: + bu.emitByVal typeLit(typ) + proc preventRvo(tree: MirTree, types: TypeEnv, changes: var Changeset) = ## Injects intermediate temporaries for assignments where the source is an ## RVO-using call rvalue and the destination potentially aliases with a @@ -822,6 +833,77 @@ proc splitAssignments(tree: MirTree, changes: var Changeset) = bu.subTree mnkMove: bu.use tmp +proc lowerCast(tree: MirTree, graph: ModuleGraph, env: var MirEnv, + changes: var Changeset) = + ## Lower cast operations between non-integer or non-pointer-like types into + ## memory copies. + + proc isRequiresCopy(types: TypeEnv, desc: HeaderId): bool {.inline.} = + var desc = desc + # XXX: skipping imported types is a workaround for ``nimCopyMem`` casting + # to ``size_t``, which would result in infinite recursion at run- + # time if turned into a ``nimCopyMem`` + while types[desc].kind == tkImported: + desc = types.get(types[desc].elem).desc[Lowered] + + types[desc].kind in {tkFloat, tkRecord, tkUnion, tkArray, tkImported} + + proc apply(bu: var MirBuilder, tree: MirTree, dst, src: NodePosition, + env: var MirEnv, op: ProcedureId) {.nimcall.} = + let dstPtr = bu.wrapTemp PointerType: + bu.subTree mnkAddr, PointerType: + bu.emitFrom(tree, dst) + let srcPtr = + if tree[skipConversions(tree, OpValue src)].kind in LiteralDataNodes: + # cannot take the address of some literal value; use a temporary + let tmp = bu.wrapTemp tree[src].typ: + bu.subTree mnkCopy, tree[src].typ: + bu.emitFrom(tree, src) + bu.wrapTemp PointerType: + bu.subTree mnkAddr, PointerType: + bu.use tmp + else: + bu.wrapTemp PointerType: + bu.subTree mnkAddr, PointerType: + bu.emitFrom(tree, src) + + let sizeOf = genSizeOf(bu, env, tree[dst].typ) + + bu.subTree mnkVoid: + bu.buildCall op, VoidType: + bu.emitByVal dstPtr + bu.emitByVal srcPtr + bu.emitByVal sizeOf + + for n in search(tree, {mnkCast}): + let + dst = env.types.get(tree[n].typ).desc[Lowered] + src = env.types.get(tree[n, 0].typ).desc[Lowered] + + # ignore the cast if between the same types + if src != dst and (isRequiresCopy(env.types, dst) or + isRequiresCopy(env.types, src)): + let + asgn = tree.parent(n) + copy = env.procedures.add(graph.getCompilerProc("nimCopyMem")) + + case tree[asgn].kind + of mnkAsgn, mnkInit: + # transform ``_1 = cast _2`` into: + # nimCopyMem(arg addr(_1), arg addr(_2), ...) + changes.replaceMulti(tree, asgn, bu): + apply(bu, tree, tree.child(asgn, 0), tree.child(n, 0), env, copy) + + of mnkDef, mnkDefCursor: + # transform ``def _1 = cast _2`` into: + # def _1 + # nimCopyMem(arg addr(_1), arg addr(_2), ...) + changes.replace(tree, n, MirNode(kind: mnkNone)) + changes.insert(tree, tree.sibling(asgn), n, bu): + apply(bu, tree, tree.child(asgn, 0), tree.child(n, 0), env, copy) + else: + unreachable() + proc applyPasses*(body: var MirBody, prc: PSym, env: var MirEnv, graph: ModuleGraph, target: TargetBackend) = ## Applies all applicable MIR passes to the body (`tree` and `source`) of @@ -860,6 +942,7 @@ proc applyPasses*(body: var MirBody, prc: PSym, env: var MirEnv, lowerChecks(body, graph, env, c) injectStrPreparation(body.code, graph, env, c) lowerCase(body.code, graph, env, c) + lowerCast(body.code, graph, env, c) # instrument the body with profiler calls after all lowerings, but before # optimization