From 93050ca65b68012bfda3223c9849937081f5c63f Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 10 Jan 2025 08:54:51 +0100 Subject: [PATCH 01/10] WIP: more required subsystems --- src/gear3/destroyer.nim | 291 ++++++++++++++++++++++ src/gear3/duplifier.nim | 536 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 827 insertions(+) create mode 100644 src/gear3/destroyer.nim create mode 100644 src/gear3/duplifier.nim diff --git a/src/gear3/destroyer.nim b/src/gear3/destroyer.nim new file mode 100644 index 00000000..17b47870 --- /dev/null +++ b/src/gear3/destroyer.nim @@ -0,0 +1,291 @@ +# +# +# Gear3 Compiler +# (c) Copyright 2025 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +##[ + +The destroyer runs after `to_stmts` as it relies on `var tmp = g()` +injections. It only destroys variables and transforms assignments. + +Statements +========== + +Assignments and var bindings need to use `=dup`. In the first version, we don't +emit `=copy`. + +`x = f()` is turned into `=destroy(x); x =bitcopy f()`. +`x = lastUse y` is turned into either + + `=destroy(x); x =bitcopy y; =wasMoved(y)` # no self assignments possible + +or + + `let tmp = y; =wasMoved(y); =destroy(x); x =bitcopy tmp` # safe for self assignments + +`x = someUse y` is turned into either + + `=destroy(x); x =bitcopy =dup(y)` # no self assignments possible + +or + + `let tmp = x; x =bitcopy =dup(y); =destroy(tmp)` # safe for self assignments + +`var x = f()` is turned into `var x = f()`. There is nothing to do because the backend +interprets this `=` as `=bitcopy`. + +`var x = lastUse y` is turned into `var x = y; =wasMoved(y)`. +`var x = someUse y` is turned into `var x = =dup(y)`. + +]## + +include nifprelude +import nifindexes, symparser, treemangler +import ".." / nimony / [nimony_model, programs, typenav] +import lifter + +const + NoLabel = SymId(-2) + AnonBlock = SymId(-3) + +type + ScopeKind = enum + Other + WhileOrBlock + DestructorOp = object + destroyProc: SymId + arg: SymId + + Scope = object + p: Program + label: SymId + thisModule: ModuleId + procStart: Cursor + kind: ScopeKind + destroyOps: seq[DestructorOp] + info: PackedLineInfo + parent: ptr Scope + lifter: ref LiftingCtx + isTopLevel: bool + +proc createNestedScope(kind: ScopeKind; parent: var Scope; info: PackedLineInfo; label = NoLabel): Scope = + Scope(p: parent.p, label: label, thisModule: parent.thisModule, procStart: parent.procStart, + kind: kind, destroyOps: @[], info: info, parent: addr(parent), lifter: parent.lifter, + isTopLevel: false) + +proc createEntryScope(p: Program; thisModule: ModuleId; lifter: ref LiftingCtx; + procStart: Cursor; info: PackedLineInfo): Scope = + Scope(p: p, label: NoLabel, thisModule: thisModule, procStart: procStart, + kind: Other, destroyOps: @[], info: info, parent: nil, lifter: lifter, + isTopLevel: true) + +proc callDestroy(c: var Scope; dest: var TokenBuf; destroyProc: SymId; arg: SymId) = + copyIntoKind dest, Call, c.info: + copyIntoSymUse dest, destroyProc, c.info + copyIntoSymUse dest, arg, c.info + +proc leaveScope(c: var Scope; dest: var TokenBuf) = + for i in countdown(c.destroyOps.high, 0): + callDestroy c, dest, c.destroyOps[i].destroyProc, c.destroyOps[i].arg + +proc leaveNamedBlock(c: var Scope; dest: var TokenBuf; label: SymId) = + #[ Consider: + + var x = f() + block: + break # do we want to destroy x here? No. + + ]# + var it = addr(c) + while it != nil and it.label != label: + leaveScope(it[], dest) + it = it.parent + if it != nil and it.label == label: + leaveScope(it[], dest) + else: + assert false, "do not know which block to leave" + +proc leaveAnonBlock(c: var Scope; dest: var TokenBuf) = + var it = addr(c) + while it != nil and it.kind != WhileOrBlock: + leaveScope(it[], dest) + it = it.parent + if it != nil and it.kind == WhileOrBlock: + leaveScope(it[], dest) + else: + assert false, "do not know which block to leave" + +proc trBreak(c: var Scope; dest: var TokenBuf; n: Cursor) = + let lab = n.firstSon + if lab.kind == SymUse: + leaveNamedBlock(c, dest, lab.symId) + else: + leaveAnonBlock(c, dest) + copyTree dest, tree, n + +proc trReturn(c: var Scope; dest: var TokenBuf; n: Cursor) = + var it = addr(c) + while it != nil: + leaveScope(it[], dest) + it = it.parent + copyTree dest, tree, n + +when not defined(nimony): + proc tr(c: var Scope; dest: var TokenBuf; n: Cursor) + +proc trLocal(c: var Scope; dest: var TokenBuf; n: Cursor) = + let r = asLocal(tree, n) + copyIntoKind(dest, n.kind, n.info): + copyTree(dest, tree, r.name) + copyTree(dest, tree, r.ex) + copyTree(dest, tree, r.pragmas) + copyTree(dest, tree, r.typ) + let localType = getType(c.p, tree, r.name) + let destructor = getDestructor(c.lifter[], localType, n.info) + let s = r.name.symId + let sk = c.p[tree.m].syms[s].kind + if destructor.s != SymId(-1) and sk != CursorDecl: + if not c.isTopLevel and sk != ResultDecl: + # XXX If we don't free global variables let's at least free temporaries! + c.destroyOps.add DestructorOp(destroyProc: destructor, arg: s) + tr c, dest, tree, r.value + +proc trScope(c: var Scope; dest: var TokenBuf; body: Cursor) = + copyIntoKind dest, StmtList, body.info: + if body.kind == StmtList: + for ch in sonsReadOnly(tree, body): + tr c, dest, tree, ch + else: + tr c, dest, tree, body + leaveScope(c, dest) + +proc registerSinkParameters(c: var Scope; params: Cursor) = + for ch in sonsFrom1(tree, params): + let r = asLocal(tree, ch) + if r.typ.kind == SinkTy: + let destructor = getDestructor(c.lifter[], FullTypeId(t: r.typ.firstSon, m: tree.id), ch.info) + if destructor.s != SymId(-1): + c.destroyOps.add DestructorOp(destroyProc: destructor, arg: r.name.symId) + +proc trProcDecl(c: var Scope; dest: var TokenBuf; n: Cursor) = + let r = asRoutine(tree, n) + var c2 = createEntryScope(c.p, c.thisModule, c.lifter, r.body, r.body.info) + c2.isTopLevel = false + copyInto(dest, n): + copyTree dest, tree, r.name + copyTree dest, tree, r.ex + copyTree dest, tree, r.pat + copyTree dest, tree, r.generics + copyTree dest, tree, r.params + copyTree dest, tree, r.pragmas + copyTree dest, tree, r.exc + if r.body.kind == StmtList and r.generics.kind != GenericParams and + not hasBuiltinPragma(c.p, tree, r.pragmas, "nodestroy"): + registerSinkParameters(c2, tree, r.params) + trScope c2, dest, tree, r.body + else: + copyTree dest, tree, r.body + +proc trNestedScope(c: var Scope; dest: var TokenBuf; body: Cursor; kind = Other) = + var bodyScope = createNestedScope(kind, c, body.info) + trScope bodyScope, dest, tree, body + +proc trWhile(c: var Scope; dest: var TokenBuf; n: Cursor) = + #[ while prop(createsObj()) + was turned into `while (let tmp = createsObj(); prop(tmp))` by `duplifier.nim` + already and `to_stmts` did turn it into: + + while true: + let tmp = createsObj() + if not prop(tmp): break + + For these reasons we don't have to do anything special with `cond`. The same + reasoning applies to `if` and `case` statements. + ]# + let (cond, body) = sons2(tree, n) + copyInto(dest, n): + tr c, dest, tree, cond + trNestedScope c, dest, tree, body, WhileOrBlock + +proc trBlock(c: var Scope; dest: var TokenBuf; n: Cursor) = + let (label, body) = sons2(tree, n) + let labelId = if label.kind == SymDef: label.symId else: AnonBlock + var bodyScope = createNestedScope(WhileOrBlock, c, body.info, labelId) + copyInto(dest, n): + copyTree dest, tree, label + trScope bodyScope, dest, tree, body + +proc trIf(c: var Scope; dest: var TokenBuf; n: Cursor) = + for ch in sons(dest, tree, n): + case ch.kind + of ElifBranch: + let (cond, action) = sons2(tree, ch) + copyInto(dest, ch): + tr c, dest, tree, cond + trNestedScope c, dest, tree, action + of ElseBranch: + copyInto(dest, ch): + trNestedScope c, dest, tree, ch.firstSon + else: + copyTree dest, tree, ch + +proc trCase(c: var Scope; dest: var TokenBuf; n: Cursor) = + copyInto(dest, n): + tr c, dest, tree, n.firstSon + for ch in sonsFrom1(tree, n): + case ch.kind + of OfBranch: + copyInto(dest, ch): + let (first, action) = sons2(tree, ch) + copyTree dest, tree, first + trNestedScope c, dest, tree, action + of ElseBranch: + copyInto(dest, ch): + trNestedScope c, dest, tree, ch.firstSon + else: + copyTree dest, tree, ch + +proc tr(c: var Scope; dest: var TokenBuf; n: Cursor) = + case n.kind + of ReturnStmt: + trReturn(c, dest, tree, n) + of BreakStmt: + trBreak(c, dest, tree, n) + of IfStmt: + trIf c, dest, tree, n + of CaseStmt: + trCase c, dest, tree, n + of BlockStmt: + trBlock c, dest, tree, n + #of Asgn, FirstAsgn: + # trAsgn c, dest, tree, n + of VarDecl, LetDecl, ConstDecl, ResultDecl: + trLocal c, dest, tree, n + of WhileStmt: + trWhile c, dest, tree, n + of DeclarativeNodes, Atoms, Pragmas, TemplateDecl, IteratorDecl, + UsingStmt, CommentStmt, BindStmt, MixinStmt, ContinueStmt: + copyTree dest, tree, n + of ProcDecl, FuncDecl, MacroDecl, MethodDecl, ConverterDecl: + trProcDecl c, dest, tree, n + else: + for ch in sons(dest, tree, n): + tr(c, dest, tree, ch) + +proc injectDestructors*(p: Program; t: TreeId; lifter: ref LiftingCtx): TreeId = + let thisModule = p[t].m + let info = p[t][StartPos].info + + var c = createEntryScope(p, thisModule, lifter, StartPos, info) + result = createTree(p, c.thisModule) + p[result].flags.incl dontTouch + + tr(c, p[result], p[t], StartPos) + leaveScope c, p[result] + genMissingHooks lifter[], p[result] + patch p[result], PatchPos(0) + p[result].flags.excl dontTouch diff --git a/src/gear3/duplifier.nim b/src/gear3/duplifier.nim new file mode 100644 index 00000000..6873f98c --- /dev/null +++ b/src/gear3/duplifier.nim @@ -0,0 +1,536 @@ +# +# +# Gear3 Compiler +# (c) Copyright 2025 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +##[ + +The duplifier runs before `to_stmts` as it makes heavy use +of `StmtListExpr`. After `to_stmts` destructor injections +and assignment rewrites are performed. This is done in `destroyer`. + +Expressions +=========== + +There are 4 cases: + +1. Owner to owner transfer (`var x = f()`): Nothing to do. +2. Owner to unowner transfer (`f g()`): Transform to `var tmp = g(); f(tmp)`. +3. Unowner to owner transfer (`f_sink(x)`): Transform to `f_sink(=dup(x))`. + Or to `f_sink(x); =wasMoved(x)`. +4. Unowner to unowner transfer (`f(x)`): Nothing to do. + +It follows that we're only interested in Call expressions here, or similar +(object constructors etc). + +]## + +include nifprelude +import nifindexes, symparser, treemangler +import ".." / nimony / [nimony_model, programs, typenav] + +type + Context = object + dest: TokenBuf + lifter: ref LiftingCtx + procStart: Cursor + localTypes: TreeId + reportLastUse: bool + + Expects = enum + DontCare, + WillBeOwned, + WantNonOwner, + WantOwner + +proc isLastRead(c: Context; n: Cursor): bool = + let r = rootOf(c.p, tree, n) + if r == SymId(-1): + result = false + else: + var canAnalyse = true + let s = c.p[tree.m].syms[r] + if s.kind == ParamDecl: + let typ = getType(c.p, tree, n) + canAnalyse = c.p[typ].kind == SinkTy + result = canAnalyse and isLastRead(c.p, tree, c.procStart, n, r) + + if c.reportLastUse: + echo infoToStr(n.info, c.p[tree.m]), " LastUse: ", result + +when not defined(nimony): + proc tr(c: var Context; n: Cursor; e: Expects) + +proc trSons(c: var Context; n: Cursor; e: Expects) = + for ch in sons(dest, tree, n): + tr c, dest, tree, ch, e + +proc isResultUsage(p: Program; n: Cursor): bool {.inline.} = + if n.kind == SymUse: + let x = accessModuleSym(p, tree, n) + result = p[x].kind == ResultDecl + else: + result = false + +proc trReturn(c: var Context; n: Cursor) = + copyInto dest, n: + let retVal = n.firstSon + if isResultUsage(c.p, tree, retVal): + copyTree dest, tree, retVal + else: + tr c, dest, tree, retVal, WantOwner + +proc evalLeftHandSide(c: var Context; le: Cursor): TokenBuf = + result = createTokenBuf(10) + if le.kind == Symbol: + # simple enough: + copyTree result, tree, le + else: + let typ = getType(c.p, tree, le) + let info = le.info + let tmp = declareSym(c.p[dest.m], VarDecl, c.p[dest.m].strings.getOrIncl("temp")) + let d = takePos(dest) + copyIntoKind dest, VarDecl, info: + addSymDef dest, tmp, info + dest.addEmpty2 info # export marker, pragma + copyIntoKind dest, PtrTy, info: # type + copyTreeX dest, c.p[typ.m], typ.t, c.p + copyIntoKind dest, HiddenAddr, info: + #copyTree dest, tree, le + tr c, dest, tree, le, DontCare + let d2 = takePos(c.p[c.localTypes]) + copyTree c.p[c.localTypes], dest, d + setDeclPosRaw c.p, c.p[dest.m].syms[tmp], FullTypeId(m: c.localTypes, t: d2) + + copyIntoKind result, HiddenDeref, info: + copyIntoSymUse result, tmp, info + +proc callDestroy(c: var Context; destroyProc: SymId; t: TokenBuf; arg: Cursor) = + let info = t[arg].info + copyIntoKind dest, Call, info: + copyIntoSymUse c.p, dest, destroyProc, info + copyTree dest, t, arg + +proc callDestroy(c: var Context; destroyProc: SymId; arg: SymId; info: PackedLineInfo) = + copyIntoKind dest, Call, info: + copyIntoSymUse c.p, dest, destroyProc, info + copyIntoSymUse dest, arg, info + +proc tempOfTrArg(c: var Context; n: Cursor; typ: FullTypeId): SymId = + let info = n.info + result = declareSym(c.p[dest.m], CursorDecl, c.p[dest.m].strings.getOrIncl("temp")) + let d = takePos(dest) + copyIntoKind dest, VarDecl, info: + addSymDef dest, result, info + dest.addEmpty2 info # export marker, pragma + copyTreeX dest, c.p[typ.m], typ.t, c.p + tr c, dest, tree, n, WillBeOwned + let d2 = takePos(c.p[c.localTypes]) + copyTree c.p[c.localTypes], dest, d + setDeclPosRaw c.p, c.p[dest.m].syms[result], FullTypeId(m: c.localTypes, t: d2) + +proc callDup(c: var Context; arg: Cursor) = + let typ = getType(c.p, t, arg) + let info = t[arg].info + let hookProc = getHook(c.lifter[], attachedDup, typ, info) + if hookProc.s != SymId(-1) and t[arg].kind != StrLit: + copyIntoKind dest, Call, info: + copyIntoSymUse c.p, dest, hookProc, info + tr c, dest, t, arg, WillBeOwned + else: + tr c, dest, t, arg, WillBeOwned + +proc callWasMoved(c: var Context; arg: Cursor) = + let typ = getType(c.p, t, arg) + let info = t[arg].info + let hookProc = getHook(c.lifter[], attachedWasMoved, typ, info) + if hookProc.s != SymId(-1): + copyIntoKind dest, Call, info: + copyIntoSymUse c.p, dest, hookProc, info + copyIntoKind dest, HiddenAddr, info: + copyTree dest, t, arg + +proc trAsgn(c: var Context; n: Cursor) = + #[ + `x = f()` is turned into `=destroy(x); x =bitcopy f()`. + `x = lastUse y` is turned into either + + `=destroy(x); x =bitcopy y; =wasMoved(y)` # no self assignments possible + + or + + `let tmp = x; x =bitcopy y; =wasMoved(y); =destroy(tmp)` # safe for self assignments + + `x = someUse y` is turned into either + + `=destroy(x); x =bitcopy =dup(y)` # no self assignments possible + + or + + `let tmp = x; x =bitcopy =dup(y); =destroy(tmp)` # safe for self assignments + ]# + let (le, ri) = sons2(tree, n) + let riType = getType(c.p, tree, ri) + let destructor = getDestructor(c.lifter[], riType, n.info) + if destructor.s == SymId(-1): + # the type has no destructor, there is nothing interesting to do: + trSons c, dest, tree, n, DontCare + + else: + let isNotFirstAsgn = n.kind == Asgn + let lhs = evalLeftHandSide(c, dest, tree, le) + if constructsValue(c.p, tree, ri): + # `x = f()` is turned into `=destroy(x); x =bitcopy f()`. + if isNotFirstAsgn: + callDestroy(c, dest, destructor, lhs, StartPos) + copyInto dest, n: + copyTree dest, lhs, StartPos + tr c, dest, tree, ri, WillBeOwned + elif isLastRead(c, tree, ri): + if isNotFirstAsgn and potentialSelfAsgn(c.p, tree, le, ri): + # `let tmp = y; =wasMoved(y); =destroy(x); x =bitcopy tmp` + let tmp = tempOfTrArg(c, dest, tree, ri, riType) + callWasMoved c, dest, tree, ri + callDestroy(c, dest, destructor, lhs, StartPos) + copyInto dest, n: + #tr c, dest, lhs, StartPos, DontCare + # XXX Fixme + copyTree dest, lhs, StartPos + copyIntoSymUse dest, tmp, ri.info + else: + if isNotFirstAsgn: + callDestroy(c, dest, destructor, lhs, StartPos) + copyInto dest, n: + copyTree dest, lhs, StartPos + tr c, dest, tree, ri, WillBeOwned + callWasMoved c, dest, tree, ri + else: + if isNotFirstAsgn and potentialSelfAsgn(c.p, tree, le, ri): + # `let tmp = x; x =bitcopy =dup(y); =destroy(tmp)` + let tmp = tempOfTrArg(c, dest, lhs, StartPos, riType) + copyInto dest, n: + tr c, dest, lhs, StartPos, DontCare + callDup c, dest, tree, ri + callDestroy(c, dest, destructor, tmp, tree[le].info) + else: + if isNotFirstAsgn: + callDestroy(c, dest, destructor, lhs, StartPos) + copyInto dest, n: + #tr c, dest, lhs, StartPos, DontCare + # XXX Fixme + copyTree dest, lhs, StartPos + #callDup c, dest, tree, ri + tr c, dest, tree, ri, WantOwner + +proc trExplicitDestroy(c: var Context; n: Cursor) = + let typ = getType(c.p, tree, n.firstSon) + let info = n.info + let destructor = getDestructor(c.lifter[], typ, info) + if destructor.s == SymId(-1): + # the type has no destructor, there is nothing interesting to do: + dest.addEmpty info + else: + copyIntoKind dest, Call, info: + copyIntoSymUse c.p, dest, destructor, info + tr c, dest, tree, n.firstSon, DontCare + +proc trExplicitDup(c: var Context; n: Cursor; e: Expects) = + let typ = getType(c.p, tree, n) + let info = n.info + let hookProc = getHook(c.lifter[], attachedDup, typ, info) + if hookProc.s != SymId(-1): + copyIntoKind dest, Call, info: + copyIntoSymUse c.p, dest, hookProc, info + tr c, dest, tree, n.firstSon, DontCare + else: + let e2 = if e == WillBeOwned: WantOwner else: e + tr c, dest, tree, n.firstSon, e2 + +proc trOnlyEssentials(c: var Context; n: Cursor) = + if isAtom(tree, n): + copyTree dest, tree, n + else: + case n.kind + of EqDestroy: trExplicitDestroy c, dest, tree, n + of EqDup: trExplicitDup c, dest, tree, n, DontCare + else: + for ch in sons(dest, tree, n): trOnlyEssentials c, dest, tree, ch + +proc trProcDecl(c: var Context; n: Cursor) = + let r = asRoutine(tree, n) + var c2 = Context(p: c.p, lifter: c.lifter, procStart: r.body, localTypes: c.localTypes) + c2.reportLastUse = compilerShouldReport(c.p, tree, r.pragmas, "lastUse") + + copyInto(dest, n): + copyTree dest, tree, r.name + copyTree dest, tree, r.ex + copyTree dest, tree, r.pat + copyTree dest, tree, r.generics + copyTree dest, tree, r.params + copyTree dest, tree, r.pragmas + copyTree dest, tree, r.exc + if r.body.kind == StmtList and r.generics.kind != GenericParams: + if hasBuiltinPragma(c.p, tree, r.pragmas, "nodestroy"): + trOnlyEssentials c2, dest, tree, r.body + else: + tr c2, dest, tree, r.body, DontCare + else: + copyTree dest, tree, r.body + +proc hasDestructor(c: Context; typ: Cursor): bool {.inline.} = + not isTrivial(c.lifter[], FullTypeId(t: typ, m: tree.id)) + +proc hasDestructor(c: Context; typ: FullTypeId): bool {.inline.} = + not isTrivial(c.lifter[], typ) + +type + OwningTemp = object + ex, st, vr: PatchPos + s: SymId + info: PackedLineInfo + +template owningTempDefault(): OwningTemp = + OwningTemp(ex: PatchPos(-1), st: PatchPos(-1), vr: PatchPos(-1), s: SymId(-1), info: UnknownLineInfo) + +proc bindToTemp(c: var Context; typ: FullTypeId; info: PackedLineInfo; kind = VarDecl): OwningTemp = + let s = declareSym(c.p[dest.m], kind, c.p[dest.m].strings.getOrIncl("temp")) + + let ex = prepare(dest, StmtListExpr, info) + copyTreeX dest, c.p[typ.m], typ.t, c.p + let st = prepare(dest, StmtList, info) + let vr = prepare(dest, VarDecl, info) + addSymDef dest, s, info + dest.addEmpty2 info # export marker, pragmas + copyTreeX dest, c.p[typ.m], typ.t, c.p # type + # copyTree dest, tree, ex # value + # value is filled in by the caller! + result = OwningTemp(ex: ex, st: st, vr: vr, s: s, info: info) + +proc finishOwningTemp(dest: var Tree; ow: OwningTemp) = + if ow.s != SymId(-1): + dest.patch ow.vr # finish the VarDecl + dest.patch ow.st # finish the StmtList + dest.copyIntoSymUse ow.s, ow.info + dest.patch ow.ex # finish the StmtListExpr + +proc trCall(c: var Context; n: Cursor; e: Expects) = + var ow = owningTempDefault() + let retType = getType(c.p, tree, n) + if hasDestructor(c, retType) and e == WantNonOwner: + ow = bindToTemp(c, dest, retType, n.info) + + var fnType = getType(c.p, tree, n.firstSon) + fnType = skipGenericInsts(c.p, fnType, {SkipNilTy, SkipObjectInstantiations}) + assert c.p[fnType].kind in ProcTyNodes + var paramIter = initSonsIter(c.p, ithSon(c.p, fnType, routineParamsPos)) + var i = 0 + for ch in sons(dest, tree, n): + var e2 = WantNonOwner + if hasCurrent(paramIter): + if i > 0 and c.p[ithSon(c.p, paramIter.current, localTypePos)].kind == SinkTy: e2 = WantOwner + next paramIter, c.p + tr c, dest, tree, ch, e2 + inc i + finishOwningTemp dest, ow + +proc trRawConstructor(c: var Context; n: Cursor; e: Expects) = + # Idioms like `echo ["ab", myvar, "xyz"]` are important to translate well. + let e2 = if e == WillBeOwned: WantOwner else: e + for ch in sons(dest, tree, n): + tr c, dest, tree, ch, e2 + +proc trConstructor(c: var Context; typ, ex: Cursor; e: Expects) = + # Idioms like `echo ["ab", myvar, "xyz"]` are important to translate well. + let e2 = if e == WillBeOwned: WantOwner else: e + copyIntoKind dest, TypedExpr, ex.info: + copyTree dest, tree, typ + for ch in sons(dest, tree, ex): + tr c, dest, tree, ch, e2 + when false: + var ow = owningTempDefault() + if hasDestructor(c, tree, typ) and e == WantNonOwner: + ow = bindToTemp(c, dest, FullTypeId(m: tree.id, t: typ), ex.info) + copyIntoKind dest, TypedExpr, ex.info: + copyTree dest, tree, typ + for ch in sons(dest, tree, ex): + tr c, dest, tree, ch, WantOwner + finishOwningTemp dest, ow + +proc trConvExpr(c: var Context; n: Cursor; e: Expects) = + let (typ, ex) = sons2(tree, n) + copyInto dest, n: + copyTree dest, tree, typ + tr c, dest, tree, ex, e + +proc trObjConstr(c: var Context; n: Cursor; e: Expects) = + var ow = owningTempDefault() + let typ = n.firstSon + if hasDestructor(c, tree, typ) and e == WantNonOwner: + ow = bindToTemp(c, dest, FullTypeId(m: tree.id, t: typ), n.info) + copyIntoKind dest, ObjConstr, n.info: + copyTree dest, tree, typ + for ch in sonsFrom1(tree, n): + let (first, second) = sons2(tree, ch) + copyIntoKind dest, ch.kind, ch.info: + copyTree dest, tree, first + tr c, dest, tree, second, WantOwner + finishOwningTemp dest, ow + +proc genLastRead(c: var Context; n: Cursor; typ: FullTypeId) = + let info = n.info + # translate it to: `(var tmp = location; wasMoved(location); tmp)` + var ow = bindToTemp(c, dest, typ, info, CursorDecl) + copyTree dest, tree, n + + dest.patch ow.vr # finish the VarDecl + + let hookProc = getHook(c.lifter[], attachedWasMoved, typ, info) + if hookProc.s != SymId(-1): + copyIntoKind dest, Call, info: + copyIntoSymUse c.p, dest, hookProc, info + copyIntoKind dest, HiddenAddr, info: + copyTree dest, tree, n + + dest.patch ow.st # finish the StmtList + dest.copyIntoSymUse ow.s, ow.info + dest.patch ow.ex # finish the StmtListExpr + +proc trLocation(c: var Context; n: Cursor; e: Expects) = + # `x` does not own its value as it can be read multiple times. + let typ = getType(c.p, tree, n) + if e == WantOwner and hasDestructor(c, typ): + if isLastRead(c, tree, n): + genLastRead(c, dest, tree, n, typ) + else: + let info = n.info + # translate `x` to `=dup(x)`: + let hookProc = getHook(c.lifter[], attachedDup, typ, info) + if hookProc.s != SymId(-1): + copyIntoKind dest, Call, info: + copyIntoSymUse c.p, dest, hookProc, info + if isAtom(tree, n): + copyTree dest, tree, n + else: + trSons c, dest, tree, n, DontCare + elif isAtom(tree, n): + copyTree dest, tree, n + else: + trSons c, dest, tree, n, DontCare + elif isAtom(tree, n): + copyTree dest, tree, n + else: + trSons c, dest, tree, n, DontCare + +proc trLocal(c: var Context; n: Cursor) = + let r = asLocal(tree, n) + var wasMovedArg = noPos + copyInto(dest, n): + copyTree dest, tree, r.name + copyTree dest, tree, r.ex + copyTree dest, tree, r.pragmas + copyTree dest, tree, r.typ + + let localType = getType(c.p, tree, r.name) + let destructor = getDestructor(c.lifter[], localType, n.info) + if destructor.s != SymId(-1): + if constructsValue(c.p, tree, r.value): + tr c, dest, tree, r.value, WillBeOwned + elif isLastRead(c, tree, r.value): + tr c, dest, tree, r.value, WillBeOwned + wasMovedArg = r.value + else: + #var v = createTree(c.p, c.thisModule) + #tr c, c.p[v], tree, val, WillBeOwned + #callDup c, dest, c.p[v], StartPos + #enforceFreeTree c.p, v + callDup c, dest, tree, r.value + else: + tr c, dest, tree, r.value, WillBeOwned + if wasMovedArg != noPos: + callWasMoved c, dest, tree, wasMovedArg + +proc trStmtListExpr(c: var Context; n: Cursor; e: Expects) = + let (t, s, x) = sons3(tree, n) + copyInto dest, n: + copyTree dest, tree, t + tr(c, dest, tree, s, WantNonOwner) + tr(c, dest, tree, x, e) + +proc trEnsureMove(c: var Context; n: Cursor; e: Expects) = + let typ = getType(c.p, tree, n) + if isLastRead(c, tree, n.firstSon): + if e == WantOwner and hasDestructor(c, typ): + copyInto dest, n: + genLastRead(c, dest, tree, n, typ) + else: + copyInto dest, n: + tr c, dest, tree, n.firstSon, e + elif constructsValue(c.p, tree, n.firstSon): + # we allow rather silly code like `ensureMove(234)`. + # Seems very useful for generic programming as this can come up + # from template expansions: + copyInto dest, n: + tr c, dest, tree, n.firstSon, e + else: + produceError dest, tree, n, c.p[tree.m], ["not the last usage of: $1"] + +proc tr(c: var Context; n: Cursor; e: Expects) = + case n.kind + of Call: + trCall c, dest, tree, n, e + of TypedExpr: + let (typ, ex) = sons2(tree, n) + if constructsValue(c.p, tree, n): + trConstructor c, dest, tree, typ, ex, e + else: + trSons c, dest, tree, n, DontCare + of ConvExpr, CastExpr: + trConvExpr c, dest, tree, n, e + of ObjConstr: + trObjConstr c, dest, tree, n, e + of SymUse, ModuleSymUse, FieldAccess, VarFieldAccess, TupleFieldAccess, PtrFieldAccess, + PtrVarFieldAccess, RefFieldAccess, RefVarFieldAccess, ArrayAt, ArrayPtrAt: + trLocation c, dest, tree, n, e + of ReturnStmt: + trReturn(c, dest, tree, n) + of Asgn, FirstAsgn: + trAsgn c, dest, tree, n + of VarDecl, LetDecl, ConstDecl, ResultDecl: + trLocal c, dest, tree, n + of DeclarativeNodes, AtomsExceptSymUse, Pragmas, TemplateDecl, IteratorDecl, + UsingStmt, CommentStmt, BindStmt, MixinStmt, ContinueStmt, BreakStmt: + copyTree dest, tree, n + of ProcDecl, FuncDecl, MacroDecl, MethodDecl, ConverterDecl: + trProcDecl c, dest, tree, n + of Par: + trSons(c, dest, tree, n, e) + of StmtListExpr: + trStmtListExpr c, dest, tree, n, e + of TupleConstr, BracketConstr: + trRawConstructor c, dest, tree, n, e + of EqDup: + trExplicitDup c, dest, tree, n, e + of EqDestroy: + trExplicitDestroy c, dest, tree, n + of EnsureMove: + trEnsureMove c, dest, tree, n, e + else: + for ch in sons(dest, tree, n): + tr(c, dest, tree, ch, WantNonOwner) + +proc injectDups*(p: Program; t: TreeId; lifter: ref LiftingCtx): TreeId = + let thisModule = p[t].m + var c = Context(p: p, lifter: lifter, procStart: StartPos, localTypes: createTokenBuf(4)) + assert c.localTypes.int > 0 + + result = createTree(p, thisModule) + p[result].flags.incl dontTouch + tr(c, p[result], p[t], StartPos, WantNonOwner) + genMissingHooks lifter[], p[result] + patch p[result], PatchPos(0) + p[result].flags.excl dontTouch + enforceFreeTree p, c.localTypes From e7e3a641c20d521e41193498b3d2c46dd4353d91 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 10 Jan 2025 11:11:17 +0100 Subject: [PATCH 02/10] progress --- src/gear3/duplifier.nim | 515 ++++++++++++++++++++---------------- src/gear3/gear3.nim | 2 +- src/nimony/nimony_model.nim | 52 ++++ src/nimony/sembasics.nim | 52 ---- 4 files changed, 336 insertions(+), 285 deletions(-) diff --git a/src/gear3/duplifier.nim b/src/gear3/duplifier.nim index 6873f98c..a0266100 100644 --- a/src/gear3/duplifier.nim +++ b/src/gear3/duplifier.nim @@ -29,17 +29,18 @@ It follows that we're only interested in Call expressions here, or similar ]## +import std / [assertions] include nifprelude -import nifindexes, symparser, treemangler -import ".." / nimony / [nimony_model, programs, typenav] +import nifindexes, symparser, treemangler, lifter +import ".." / nimony / [nimony_model, programs, decls, typenav] type Context = object dest: TokenBuf lifter: ref LiftingCtx - procStart: Cursor - localTypes: TreeId reportLastUse: bool + typeCache: TypeCache + tmpCounter: int Expects = enum DontCare, @@ -47,114 +48,160 @@ type WantNonOwner, WantOwner +# -------------- helpers ---------------------------------------- + proc isLastRead(c: Context; n: Cursor): bool = - let r = rootOf(c.p, tree, n) - if r == SymId(-1): + # XXX We don't have a move analyser yet. + false + +const + ConstructingExprs = {CallX, CallStrLitX, InfixX, PrefixX, CmdX, OconstrX, AconstrX, TupleConstrX} + +proc constructsValue*(n: Cursor): bool = + var n = n + while true: + case n.exprKind + of CastX, ConvX, HconvX, DconvX, OconvX: + inc n + skip n + of ExprX: + inc n + while not isLastSon(n): skip n + else: break + result = n.exprKind in ConstructingExprs + +proc rootOf(n: Cursor): SymId = + var n = n + while n.exprKind in {DotX, TupAtX, AtX}: + n = n.firstSon + if n.kind == Symbol: + result = n.symId + else: + result = NoSymId + +proc potentialSelfAsgn(dest, src: Cursor): bool = + # if `x.fieldA` and `*.fieldB` it is not a self assignment. + # if `x` and `y` and `x != y` it is not a self assignment. + if src.exprKind in ConstructingExprs: result = false else: - var canAnalyse = true - let s = c.p[tree.m].syms[r] - if s.kind == ParamDecl: - let typ = getType(c.p, tree, n) - canAnalyse = c.p[typ].kind == SinkTy - result = canAnalyse and isLastRead(c.p, tree, c.procStart, n, r) + result = true # true until proven otherwise + let d = rootOf(dest) + let s = rootOf(src) + if d != NoSymId or s != NoSymId: + # one of the expressions was analysable + if d == s: + # see if we can distinguish between `x.fieldA` and `x.fieldB` which + # cannot alias. We do know here that both expressions are free of + # pointer derefs, so we can simply use `sameValues` here. + result = sameTrees(dest, src) + else: + # different roots while we know that at least one expression has + # no harmful pointer deref: + result = false - if c.reportLastUse: - echo infoToStr(n.info, c.p[tree.m]), " LastUse: ", result +# ----------------------------------------------------------- when not defined(nimony): - proc tr(c: var Context; n: Cursor; e: Expects) - -proc trSons(c: var Context; n: Cursor; e: Expects) = - for ch in sons(dest, tree, n): - tr c, dest, tree, ch, e - -proc isResultUsage(p: Program; n: Cursor): bool {.inline.} = - if n.kind == SymUse: - let x = accessModuleSym(p, tree, n) - result = p[x].kind == ResultDecl + proc tr(c: var Context; n: var Cursor; e: Expects) + +proc trSons(c: var Context; n: var Cursor; e: Expects) = + while n.kind != ParRi: + tr c, n, e + +proc isResultUsage(n: Cursor): bool {.inline.} = + result = false + if n.kind == Symbol: + let res = tryLoadSym(n.symId) + if res.status == LacksNothing: + let r = asLocal(res.decl) + result = r.kind == ResultY + +proc wantParRi(dest: var TokenBuf; n: var Cursor) = + if n.kind == ParRi: + dest.add n + inc n else: - result = false - -proc trReturn(c: var Context; n: Cursor) = - copyInto dest, n: - let retVal = n.firstSon - if isResultUsage(c.p, tree, retVal): - copyTree dest, tree, retVal + error "expected ')', but got: ", n + +template copyInto*(dest: var TokenBuf; n: var Cursor; body: untyped) = + assert n.kind == ParLe + dest.add n + inc n + body + wantParRi dest, n + +proc trReturn(c: var Context; n: var Cursor) = + copyInto c.dest, n: + if isResultUsage(n): + copyTree c.dest, n else: - tr c, dest, tree, retVal, WantOwner + tr c, n, WantOwner -proc evalLeftHandSide(c: var Context; le: Cursor): TokenBuf = +proc evalLeftHandSide(c: var Context; le: var Cursor): TokenBuf = result = createTokenBuf(10) if le.kind == Symbol: # simple enough: - copyTree result, tree, le + takeTree result, le else: - let typ = getType(c.p, tree, le) + let typ = getType(c.typeCache, le) let info = le.info - let tmp = declareSym(c.p[dest.m], VarDecl, c.p[dest.m].strings.getOrIncl("temp")) - let d = takePos(dest) - copyIntoKind dest, VarDecl, info: - addSymDef dest, tmp, info - dest.addEmpty2 info # export marker, pragma - copyIntoKind dest, PtrTy, info: # type - copyTreeX dest, c.p[typ.m], typ.t, c.p - copyIntoKind dest, HiddenAddr, info: - #copyTree dest, tree, le - tr c, dest, tree, le, DontCare - let d2 = takePos(c.p[c.localTypes]) - copyTree c.p[c.localTypes], dest, d - setDeclPosRaw c.p, c.p[dest.m].syms[tmp], FullTypeId(m: c.localTypes, t: d2) - - copyIntoKind result, HiddenDeref, info: + let tmp = pool.syms.getOrIncl("`lhs." & $c.tmpCounter) + inc c.tmpCounter + copyIntoKind c.dest, VarS, info: + addSymDef c.dest, tmp, info + c.dest.addEmpty2 info # export marker, pragma + copyIntoKind c.dest, PtrT, info: # type + copyTree c.dest, typ + copyIntoKind c.dest, AddrX, info: + tr c, le, DontCare + + copyIntoKind result, DerefX, info: copyIntoSymUse result, tmp, info -proc callDestroy(c: var Context; destroyProc: SymId; t: TokenBuf; arg: Cursor) = - let info = t[arg].info - copyIntoKind dest, Call, info: - copyIntoSymUse c.p, dest, destroyProc, info - copyTree dest, t, arg +proc callDestroy(c: var Context; destroyProc: SymId; arg: TokenBuf) = + let info = arg[0].info + copyIntoKind c.dest, CallS, info: + copyIntoSymUse c.dest, destroyProc, info + copyTree c.dest, arg proc callDestroy(c: var Context; destroyProc: SymId; arg: SymId; info: PackedLineInfo) = - copyIntoKind dest, Call, info: - copyIntoSymUse c.p, dest, destroyProc, info - copyIntoSymUse dest, arg, info + copyIntoKind c.dest, CallS, info: + copyIntoSymUse c.dest, destroyProc, info + copyIntoSymUse c.dest, arg, info -proc tempOfTrArg(c: var Context; n: Cursor; typ: FullTypeId): SymId = +proc tempOfTrArg(c: var Context; n: var Cursor; typ: Cursor): SymId = let info = n.info - result = declareSym(c.p[dest.m], CursorDecl, c.p[dest.m].strings.getOrIncl("temp")) - let d = takePos(dest) - copyIntoKind dest, VarDecl, info: - addSymDef dest, result, info - dest.addEmpty2 info # export marker, pragma - copyTreeX dest, c.p[typ.m], typ.t, c.p - tr c, dest, tree, n, WillBeOwned - let d2 = takePos(c.p[c.localTypes]) - copyTree c.p[c.localTypes], dest, d - setDeclPosRaw c.p, c.p[dest.m].syms[result], FullTypeId(m: c.localTypes, t: d2) - -proc callDup(c: var Context; arg: Cursor) = - let typ = getType(c.p, t, arg) - let info = t[arg].info + result = pool.syms.getOrIncl("`lhs." & $c.tmpCounter) + inc c.tmpCounter + copyIntoKind c.dest, VarS, info: + addSymDef c.dest, result, info + c.dest.addEmpty2 info # export marker, pragma + copyTree c.dest, typ + tr c, n, WillBeOwned + +proc callDup(c: var Context; arg: var Cursor) = + let typ = getType(c.typeCache, arg) + let info = arg.info let hookProc = getHook(c.lifter[], attachedDup, typ, info) - if hookProc.s != SymId(-1) and t[arg].kind != StrLit: - copyIntoKind dest, Call, info: - copyIntoSymUse c.p, dest, hookProc, info - tr c, dest, t, arg, WillBeOwned + if hookProc != NoSymId and arg.kind != StringLit: + copyIntoKind c.dest, CallS, info: + copyIntoSymUse c.dest, hookProc, info + tr c, arg, WillBeOwned else: - tr c, dest, t, arg, WillBeOwned + tr c, arg, WillBeOwned -proc callWasMoved(c: var Context; arg: Cursor) = - let typ = getType(c.p, t, arg) - let info = t[arg].info +proc callWasMoved(c: var Context; arg: var Cursor) = + let typ = getType(c.typeCache, arg) + let info = arg.info let hookProc = getHook(c.lifter[], attachedWasMoved, typ, info) - if hookProc.s != SymId(-1): - copyIntoKind dest, Call, info: - copyIntoSymUse c.p, dest, hookProc, info - copyIntoKind dest, HiddenAddr, info: - copyTree dest, t, arg + if hookProc != NoSymId: + copyIntoKind c.dest, CallS, info: + copyIntoSymUse c.dest, hookProc, info + copyIntoKind c.dest, HaddrX, info: + copyTree c.dest, arg -proc trAsgn(c: var Context; n: Cursor) = +proc trAsgn(c: var Context; n: var Cursor) = #[ `x = f()` is turned into `=destroy(x); x =bitcopy f()`. `x = lastUse y` is turned into either @@ -173,113 +220,123 @@ proc trAsgn(c: var Context; n: Cursor) = `let tmp = x; x =bitcopy =dup(y); =destroy(tmp)` # safe for self assignments ]# - let (le, ri) = sons2(tree, n) - let riType = getType(c.p, tree, ri) + var n2 = n + inc n2 # AsgnS + let le = n2 + skip n2 + let ri = n2 + let riType = getType(c.typeCache, ri) let destructor = getDestructor(c.lifter[], riType, n.info) - if destructor.s == SymId(-1): + if destructor == NoSymId: # the type has no destructor, there is nothing interesting to do: - trSons c, dest, tree, n, DontCare + trSons c, n, DontCare else: - let isNotFirstAsgn = n.kind == Asgn - let lhs = evalLeftHandSide(c, dest, tree, le) - if constructsValue(c.p, tree, ri): + let isNotFirstAsgn = true # XXX Adapt this once we have "isFirstAsgn" analysis + var leCopy = le + let lhs = evalLeftHandSide(c, leCopy) + if constructsValue(ri): # `x = f()` is turned into `=destroy(x); x =bitcopy f()`. if isNotFirstAsgn: - callDestroy(c, dest, destructor, lhs, StartPos) - copyInto dest, n: - copyTree dest, lhs, StartPos - tr c, dest, tree, ri, WillBeOwned - elif isLastRead(c, tree, ri): - if isNotFirstAsgn and potentialSelfAsgn(c.p, tree, le, ri): + callDestroy(c, destructor, lhs) + copyInto c.dest, n: + copyTree c.dest, lhs + n = ri + tr c, n, WillBeOwned + wantParRi c.dest, n + elif isLastRead(c, ri): + if isNotFirstAsgn and potentialSelfAsgn(le, ri): # `let tmp = y; =wasMoved(y); =destroy(x); x =bitcopy tmp` - let tmp = tempOfTrArg(c, dest, tree, ri, riType) - callWasMoved c, dest, tree, ri - callDestroy(c, dest, destructor, lhs, StartPos) + let tmp = tempOfTrArg(c, dest, ri, riType) + callWasMoved c, dest, ri + callDestroy(c, dest, destructor, lhs) copyInto dest, n: - #tr c, dest, lhs, StartPos, DontCare + #tr c, dest, lhs, DontCare # XXX Fixme - copyTree dest, lhs, StartPos + copyTree dest, lhs copyIntoSymUse dest, tmp, ri.info else: if isNotFirstAsgn: - callDestroy(c, dest, destructor, lhs, StartPos) + callDestroy(c, dest, destructor, lhs) copyInto dest, n: - copyTree dest, lhs, StartPos - tr c, dest, tree, ri, WillBeOwned - callWasMoved c, dest, tree, ri + copyTree dest, lhs + tr c, dest, ri, WillBeOwned + callWasMoved c, dest, ri else: - if isNotFirstAsgn and potentialSelfAsgn(c.p, tree, le, ri): + if isNotFirstAsgn and potentialSelfAsgn(le, ri): # `let tmp = x; x =bitcopy =dup(y); =destroy(tmp)` - let tmp = tempOfTrArg(c, dest, lhs, StartPos, riType) + let tmp = tempOfTrArg(c, dest, lhs, riType) copyInto dest, n: - tr c, dest, lhs, StartPos, DontCare - callDup c, dest, tree, ri - callDestroy(c, dest, destructor, tmp, tree[le].info) + tr c, dest, lhs, DontCare + callDup c, dest, ri + callDestroy(c, dest, destructor, tmp[le].info) else: if isNotFirstAsgn: - callDestroy(c, dest, destructor, lhs, StartPos) + callDestroy(c, dest, destructor, lhs) copyInto dest, n: - #tr c, dest, lhs, StartPos, DontCare + #tr c, dest, lhs, DontCare # XXX Fixme - copyTree dest, lhs, StartPos - #callDup c, dest, tree, ri - tr c, dest, tree, ri, WantOwner + copyTree dest, lhs + #callDup c, dest, ri + tr c, dest, ri, WantOwner + proc trExplicitDestroy(c: var Context; n: Cursor) = - let typ = getType(c.p, tree, n.firstSon) + let typ = getType(c.p, n.firstSon) let info = n.info let destructor = getDestructor(c.lifter[], typ, info) - if destructor.s == SymId(-1): + if destructor == NoSymId: # the type has no destructor, there is nothing interesting to do: - dest.addEmpty info + c.dest.addEmpty info else: - copyIntoKind dest, Call, info: - copyIntoSymUse c.p, dest, destructor, info - tr c, dest, tree, n.firstSon, DontCare + copyIntoKind c.dest, CallS, info: + copyIntoSymUse c.dest, destructor, info + inc n + tr c, n, DontCare + skipParRi n proc trExplicitDup(c: var Context; n: Cursor; e: Expects) = - let typ = getType(c.p, tree, n) + let typ = getType(c.p, n) let info = n.info let hookProc = getHook(c.lifter[], attachedDup, typ, info) if hookProc.s != SymId(-1): copyIntoKind dest, Call, info: copyIntoSymUse c.p, dest, hookProc, info - tr c, dest, tree, n.firstSon, DontCare + tr c, dest, n.firstSon, DontCare else: let e2 = if e == WillBeOwned: WantOwner else: e - tr c, dest, tree, n.firstSon, e2 + tr c, dest, n.firstSon, e2 proc trOnlyEssentials(c: var Context; n: Cursor) = if isAtom(tree, n): - copyTree dest, tree, n + copyTree dest, n else: case n.kind - of EqDestroy: trExplicitDestroy c, dest, tree, n - of EqDup: trExplicitDup c, dest, tree, n, DontCare + of EqDestroy: trExplicitDestroy c, dest, n + of EqDup: trExplicitDup c, dest, n, DontCare else: - for ch in sons(dest, tree, n): trOnlyEssentials c, dest, tree, ch + for ch in sons(dest, n): trOnlyEssentials c, dest, ch proc trProcDecl(c: var Context; n: Cursor) = let r = asRoutine(tree, n) - var c2 = Context(p: c.p, lifter: c.lifter, procStart: r.body, localTypes: c.localTypes) - c2.reportLastUse = compilerShouldReport(c.p, tree, r.pragmas, "lastUse") + var c2 = Context(p: c.p, lifter: c.lifter, localTypes: c.localTypes) + c2.reportLastUse = compilerShouldReport(c.p, r.pragmas, "lastUse") copyInto(dest, n): - copyTree dest, tree, r.name - copyTree dest, tree, r.ex - copyTree dest, tree, r.pat - copyTree dest, tree, r.generics - copyTree dest, tree, r.params - copyTree dest, tree, r.pragmas - copyTree dest, tree, r.exc + copyTree dest, r.name + copyTree dest, r.ex + copyTree dest, r.pat + copyTree dest, r.generics + copyTree dest, r.params + copyTree dest, r.pragmas + copyTree dest, r.exc if r.body.kind == StmtList and r.generics.kind != GenericParams: - if hasBuiltinPragma(c.p, tree, r.pragmas, "nodestroy"): - trOnlyEssentials c2, dest, tree, r.body + if hasBuiltinPragma(c.p, r.pragmas, "nodestroy"): + trOnlyEssentials c2, dest, r.body else: - tr c2, dest, tree, r.body, DontCare + tr c2, dest, r.body, DontCare else: - copyTree dest, tree, r.body + copyTree dest, r.body proc hasDestructor(c: Context; typ: Cursor): bool {.inline.} = not isTrivial(c.lifter[], FullTypeId(t: typ, m: tree.id)) @@ -306,7 +363,7 @@ proc bindToTemp(c: var Context; typ: FullTypeId; info: PackedLineInfo; kind = Va addSymDef dest, s, info dest.addEmpty2 info # export marker, pragmas copyTreeX dest, c.p[typ.m], typ.t, c.p # type - # copyTree dest, tree, ex # value + # copyTree dest, ex # value # value is filled in by the caller! result = OwningTemp(ex: ex, st: st, vr: vr, s: s, info: info) @@ -319,72 +376,72 @@ proc finishOwningTemp(dest: var Tree; ow: OwningTemp) = proc trCall(c: var Context; n: Cursor; e: Expects) = var ow = owningTempDefault() - let retType = getType(c.p, tree, n) + let retType = getType(c.p, n) if hasDestructor(c, retType) and e == WantNonOwner: ow = bindToTemp(c, dest, retType, n.info) - var fnType = getType(c.p, tree, n.firstSon) + var fnType = getType(c.p, n.firstSon) fnType = skipGenericInsts(c.p, fnType, {SkipNilTy, SkipObjectInstantiations}) assert c.p[fnType].kind in ProcTyNodes var paramIter = initSonsIter(c.p, ithSon(c.p, fnType, routineParamsPos)) var i = 0 - for ch in sons(dest, tree, n): + for ch in sons(dest, n): var e2 = WantNonOwner if hasCurrent(paramIter): if i > 0 and c.p[ithSon(c.p, paramIter.current, localTypePos)].kind == SinkTy: e2 = WantOwner next paramIter, c.p - tr c, dest, tree, ch, e2 + tr c, dest, ch, e2 inc i finishOwningTemp dest, ow proc trRawConstructor(c: var Context; n: Cursor; e: Expects) = # Idioms like `echo ["ab", myvar, "xyz"]` are important to translate well. let e2 = if e == WillBeOwned: WantOwner else: e - for ch in sons(dest, tree, n): - tr c, dest, tree, ch, e2 + for ch in sons(dest, n): + tr c, dest, ch, e2 proc trConstructor(c: var Context; typ, ex: Cursor; e: Expects) = # Idioms like `echo ["ab", myvar, "xyz"]` are important to translate well. let e2 = if e == WillBeOwned: WantOwner else: e copyIntoKind dest, TypedExpr, ex.info: - copyTree dest, tree, typ - for ch in sons(dest, tree, ex): - tr c, dest, tree, ch, e2 + copyTree dest, typ + for ch in sons(dest, ex): + tr c, dest, ch, e2 when false: var ow = owningTempDefault() - if hasDestructor(c, tree, typ) and e == WantNonOwner: + if hasDestructor(c, typ) and e == WantNonOwner: ow = bindToTemp(c, dest, FullTypeId(m: tree.id, t: typ), ex.info) copyIntoKind dest, TypedExpr, ex.info: - copyTree dest, tree, typ - for ch in sons(dest, tree, ex): - tr c, dest, tree, ch, WantOwner + copyTree dest, typ + for ch in sons(dest, ex): + tr c, dest, ch, WantOwner finishOwningTemp dest, ow proc trConvExpr(c: var Context; n: Cursor; e: Expects) = let (typ, ex) = sons2(tree, n) copyInto dest, n: - copyTree dest, tree, typ - tr c, dest, tree, ex, e + copyTree dest, typ + tr c, dest, ex, e proc trObjConstr(c: var Context; n: Cursor; e: Expects) = var ow = owningTempDefault() let typ = n.firstSon - if hasDestructor(c, tree, typ) and e == WantNonOwner: + if hasDestructor(c, typ) and e == WantNonOwner: ow = bindToTemp(c, dest, FullTypeId(m: tree.id, t: typ), n.info) copyIntoKind dest, ObjConstr, n.info: - copyTree dest, tree, typ + copyTree dest, typ for ch in sonsFrom1(tree, n): let (first, second) = sons2(tree, ch) copyIntoKind dest, ch.kind, ch.info: - copyTree dest, tree, first - tr c, dest, tree, second, WantOwner + copyTree dest, first + tr c, dest, second, WantOwner finishOwningTemp dest, ow proc genLastRead(c: var Context; n: Cursor; typ: FullTypeId) = let info = n.info # translate it to: `(var tmp = location; wasMoved(location); tmp)` var ow = bindToTemp(c, dest, typ, info, CursorDecl) - copyTree dest, tree, n + copyTree dest, n dest.patch ow.vr # finish the VarDecl @@ -393,7 +450,7 @@ proc genLastRead(c: var Context; n: Cursor; typ: FullTypeId) = copyIntoKind dest, Call, info: copyIntoSymUse c.p, dest, hookProc, info copyIntoKind dest, HiddenAddr, info: - copyTree dest, tree, n + copyTree dest, n dest.patch ow.st # finish the StmtList dest.copyIntoSymUse ow.s, ow.info @@ -401,10 +458,10 @@ proc genLastRead(c: var Context; n: Cursor; typ: FullTypeId) = proc trLocation(c: var Context; n: Cursor; e: Expects) = # `x` does not own its value as it can be read multiple times. - let typ = getType(c.p, tree, n) + let typ = getType(c.p, n) if e == WantOwner and hasDestructor(c, typ): - if isLastRead(c, tree, n): - genLastRead(c, dest, tree, n, typ) + if isLastRead(c, n): + genLastRead(c, dest, n, typ) else: let info = n.info # translate `x` to `=dup(x)`: @@ -413,124 +470,118 @@ proc trLocation(c: var Context; n: Cursor; e: Expects) = copyIntoKind dest, Call, info: copyIntoSymUse c.p, dest, hookProc, info if isAtom(tree, n): - copyTree dest, tree, n + copyTree dest, n else: - trSons c, dest, tree, n, DontCare + trSons c, dest, n, DontCare elif isAtom(tree, n): - copyTree dest, tree, n + copyTree dest, n else: - trSons c, dest, tree, n, DontCare + trSons c, dest, n, DontCare elif isAtom(tree, n): - copyTree dest, tree, n + copyTree dest, n else: - trSons c, dest, tree, n, DontCare + trSons c, dest, n, DontCare proc trLocal(c: var Context; n: Cursor) = let r = asLocal(tree, n) var wasMovedArg = noPos copyInto(dest, n): - copyTree dest, tree, r.name - copyTree dest, tree, r.ex - copyTree dest, tree, r.pragmas - copyTree dest, tree, r.typ + copyTree dest, r.name + copyTree dest, r.ex + copyTree dest, r.pragmas + copyTree dest, r.typ - let localType = getType(c.p, tree, r.name) + let localType = getType(c.p, r.name) let destructor = getDestructor(c.lifter[], localType, n.info) if destructor.s != SymId(-1): - if constructsValue(c.p, tree, r.value): - tr c, dest, tree, r.value, WillBeOwned - elif isLastRead(c, tree, r.value): - tr c, dest, tree, r.value, WillBeOwned + if constructsValue(c.p, r.value): + tr c, dest, r.value, WillBeOwned + elif isLastRead(c, r.value): + tr c, dest, r.value, WillBeOwned wasMovedArg = r.value else: - #var v = createTree(c.p, c.thisModule) - #tr c, c.p[v], tree, val, WillBeOwned - #callDup c, dest, c.p[v], StartPos - #enforceFreeTree c.p, v - callDup c, dest, tree, r.value + callDup c, dest, r.value else: - tr c, dest, tree, r.value, WillBeOwned + tr c, dest, r.value, WillBeOwned if wasMovedArg != noPos: - callWasMoved c, dest, tree, wasMovedArg + callWasMoved c, dest, wasMovedArg proc trStmtListExpr(c: var Context; n: Cursor; e: Expects) = let (t, s, x) = sons3(tree, n) copyInto dest, n: - copyTree dest, tree, t - tr(c, dest, tree, s, WantNonOwner) - tr(c, dest, tree, x, e) + copyTree dest, t + tr(c, dest, s, WantNonOwner) + tr(c, dest, x, e) proc trEnsureMove(c: var Context; n: Cursor; e: Expects) = - let typ = getType(c.p, tree, n) - if isLastRead(c, tree, n.firstSon): + let typ = getType(c.p, n) + if isLastRead(c, n.firstSon): if e == WantOwner and hasDestructor(c, typ): copyInto dest, n: - genLastRead(c, dest, tree, n, typ) + genLastRead(c, dest, n, typ) else: copyInto dest, n: - tr c, dest, tree, n.firstSon, e - elif constructsValue(c.p, tree, n.firstSon): + tr c, dest, n.firstSon, e + elif constructsValue(c.p, n.firstSon): # we allow rather silly code like `ensureMove(234)`. # Seems very useful for generic programming as this can come up # from template expansions: copyInto dest, n: - tr c, dest, tree, n.firstSon, e + tr c, dest, n.firstSon, e else: - produceError dest, tree, n, c.p[tree.m], ["not the last usage of: $1"] + produceError dest, n, c.p[tree.m], ["not the last usage of: $1"] proc tr(c: var Context; n: Cursor; e: Expects) = case n.kind of Call: - trCall c, dest, tree, n, e + trCall c, dest, n, e of TypedExpr: let (typ, ex) = sons2(tree, n) - if constructsValue(c.p, tree, n): - trConstructor c, dest, tree, typ, ex, e + if constructsValue(c.p, n): + trConstructor c, dest, typ, ex, e else: - trSons c, dest, tree, n, DontCare + trSons c, dest, n, DontCare of ConvExpr, CastExpr: - trConvExpr c, dest, tree, n, e + trConvExpr c, dest, n, e of ObjConstr: - trObjConstr c, dest, tree, n, e + trObjConstr c, dest, n, e of SymUse, ModuleSymUse, FieldAccess, VarFieldAccess, TupleFieldAccess, PtrFieldAccess, PtrVarFieldAccess, RefFieldAccess, RefVarFieldAccess, ArrayAt, ArrayPtrAt: - trLocation c, dest, tree, n, e + trLocation c, dest, n, e of ReturnStmt: - trReturn(c, dest, tree, n) + trReturn(c, dest, n) of Asgn, FirstAsgn: - trAsgn c, dest, tree, n + trAsgn c, dest, n of VarDecl, LetDecl, ConstDecl, ResultDecl: - trLocal c, dest, tree, n + trLocal c, dest, n of DeclarativeNodes, AtomsExceptSymUse, Pragmas, TemplateDecl, IteratorDecl, UsingStmt, CommentStmt, BindStmt, MixinStmt, ContinueStmt, BreakStmt: - copyTree dest, tree, n + copyTree dest, n of ProcDecl, FuncDecl, MacroDecl, MethodDecl, ConverterDecl: - trProcDecl c, dest, tree, n + trProcDecl c, dest, n of Par: - trSons(c, dest, tree, n, e) + trSons(c, dest, n, e) of StmtListExpr: - trStmtListExpr c, dest, tree, n, e + trStmtListExpr c, dest, n, e of TupleConstr, BracketConstr: - trRawConstructor c, dest, tree, n, e + trRawConstructor c, dest, n, e of EqDup: - trExplicitDup c, dest, tree, n, e + trExplicitDup c, dest, n, e of EqDestroy: - trExplicitDestroy c, dest, tree, n + trExplicitDestroy c, dest, n of EnsureMove: - trEnsureMove c, dest, tree, n, e + trEnsureMove c, dest, n, e else: - for ch in sons(dest, tree, n): - tr(c, dest, tree, ch, WantNonOwner) + takeToken c.dest, n + while n.kind != ParRi: + tr(c, n, WantNonOwner) + wantParRi c.dest, n -proc injectDups*(p: Program; t: TreeId; lifter: ref LiftingCtx): TreeId = - let thisModule = p[t].m - var c = Context(p: p, lifter: lifter, procStart: StartPos, localTypes: createTokenBuf(4)) - assert c.localTypes.int > 0 +proc injectDups*(n: Cursor; lifter: ref LiftingCtx): TokenBuf = + var c = Context(lifter: lifter, typeCache: createTypeCache()) result = createTree(p, thisModule) - p[result].flags.incl dontTouch - tr(c, p[result], p[t], StartPos, WantNonOwner) - genMissingHooks lifter[], p[result] - patch p[result], PatchPos(0) - p[result].flags.excl dontTouch - enforceFreeTree p, c.localTypes + tr(c, n, WantNonOwner) + genMissingHooks lifter[] + + result = ensureMove(c.dest) diff --git a/src/gear3/gear3.nim b/src/gear3/gear3.nim index f61f48a2..ec173ef4 100644 --- a/src/gear3/gear3.nim +++ b/src/gear3/gear3.nim @@ -30,7 +30,7 @@ Gear 3 accepts Gear 2's grammar. ]## import std / [parseopt, strutils, os, osproc, tables, assertions, syncio] -import expander, lifter #, duplifier, destroyer +import expander, lifter, duplifier #, destroyer const Version = "0.2" diff --git a/src/nimony/nimony_model.nim b/src/nimony/nimony_model.nim index 292d75f2..fca65835 100644 --- a/src/nimony/nimony_model.nim +++ b/src/nimony/nimony_model.nim @@ -364,6 +364,58 @@ proc addEmpty3*(dest: var TokenBuf; info: PackedLineInfo = NoLineInfo) = dest.add dotToken(info) dest.add dotToken(info) +proc takeTree*(dest: var TokenBuf; n: var Cursor) = + if n.kind != ParLe: + dest.add n + inc n + else: + var nested = 0 + while true: + dest.add n + case n.kind + of ParLe: inc nested + of ParRi: + dec nested + if nested == 0: + inc n + break + of EofToken: + raiseAssert "expected ')', but EOF reached" + else: discard + inc n + +proc sameTrees*(a, b: Cursor): bool = + var a = a + var b = b + var nested = 0 + let isAtom = a.kind != ParLe + while true: + if a.kind != b.kind: return false + case a.kind + of ParLe: + if a.tagId != b.tagId: return false + inc nested + of ParRi: + dec nested + if nested == 0: return true + of Symbol, SymbolDef: + if a.symId != b.symId: return false + of IntLit: + if a.intId != b.intId: return false + of UIntLit: + if a.uintId != b.uintId: return false + of FloatLit: + if a.floatId != b.floatId: return false + of StringLit, Ident: + if a.litId != b.litId: return false + of CharLit, UnknownToken: + if a.uoperand != b.uoperand: return false + of DotToken, EofToken: discard "nothing else to compare" + if isAtom: return true + inc a + inc b + return false + proc isDeclarative*(n: Cursor): bool = case n.stmtKind of FromImportS, ImportS, ExportS, IncludeS, ImportExceptS, TypeS, CommentS, TemplateS: diff --git a/src/nimony/sembasics.nim b/src/nimony/sembasics.nim index 35894044..78175036 100644 --- a/src/nimony/sembasics.nim +++ b/src/nimony/sembasics.nim @@ -219,38 +219,6 @@ proc typeToCanon*(buf: TokenBuf; start: int): string = result.add " f" result.addInt buf[i].floatId.int -proc sameTrees*(a, b: TypeCursor): bool = - var a = a - var b = b - var nested = 0 - let isAtom = a.kind != ParLe - while true: - if a.kind != b.kind: return false - case a.kind - of ParLe: - if a.tagId != b.tagId: return false - inc nested - of ParRi: - dec nested - if nested == 0: return true - of Symbol, SymbolDef: - if a.symId != b.symId: return false - of IntLit: - if a.intId != b.intId: return false - of UIntLit: - if a.uintId != b.uintId: return false - of FloatLit: - if a.floatId != b.floatId: return false - of StringLit, Ident: - if a.litId != b.litId: return false - of CharLit, UnknownToken: - if a.uoperand != b.uoperand: return false - of DotToken, EofToken: discard "nothing else to compare" - if isAtom: return true - inc a - inc b - return false - proc typeToCursor*(c: var SemContext; buf: TokenBuf; start: int): TypeCursor = let key = typeToCanon(buf, start) if c.typeMem.hasKey(key): @@ -447,26 +415,6 @@ proc publishSignature*(c: var SemContext; s: SymId; start: int) = # ------------------------------------------------------------------------------------------------- -proc takeTree*(dest: var TokenBuf; n: var Cursor) = - if n.kind != ParLe: - dest.add n - inc n - else: - var nested = 0 - while true: - dest.add n - case n.kind - of ParLe: inc nested - of ParRi: - dec nested - if nested == 0: - inc n - break - of EofToken: - error "expected ')', but EOF reached" - else: discard - inc n - proc takeTree*(c: var SemContext; n: var Cursor) = takeTree c.dest, n From b226aaee3eaf8e30323cdabc258a6d6e2e3d5b88 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 10 Jan 2025 17:53:29 +0100 Subject: [PATCH 03/10] progress --- src/gear3/duplifier.nim | 58 ++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/gear3/duplifier.nim b/src/gear3/duplifier.nim index a0266100..3165888b 100644 --- a/src/gear3/duplifier.nim +++ b/src/gear3/duplifier.nim @@ -170,7 +170,8 @@ proc callDestroy(c: var Context; destroyProc: SymId; arg: SymId; info: PackedLin copyIntoSymUse c.dest, destroyProc, info copyIntoSymUse c.dest, arg, info -proc tempOfTrArg(c: var Context; n: var Cursor; typ: Cursor): SymId = +proc tempOfTrArg(c: var Context; n: Cursor; typ: Cursor): SymId = + var n = n let info = n.info result = pool.syms.getOrIncl("`lhs." & $c.tmpCounter) inc c.tmpCounter @@ -191,7 +192,7 @@ proc callDup(c: var Context; arg: var Cursor) = else: tr c, arg, WillBeOwned -proc callWasMoved(c: var Context; arg: var Cursor) = +proc callWasMoved(c: var Context; arg: Cursor) = let typ = getType(c.typeCache, arg) let info = arg.info let hookProc = getHook(c.lifter[], attachedWasMoved, typ, info) @@ -234,7 +235,7 @@ proc trAsgn(c: var Context; n: var Cursor) = else: let isNotFirstAsgn = true # XXX Adapt this once we have "isFirstAsgn" analysis var leCopy = le - let lhs = evalLeftHandSide(c, leCopy) + var lhs = evalLeftHandSide(c, leCopy) if constructsValue(ri): # `x = f()` is turned into `=destroy(x); x =bitcopy f()`. if isNotFirstAsgn: @@ -243,42 +244,45 @@ proc trAsgn(c: var Context; n: var Cursor) = copyTree c.dest, lhs n = ri tr c, n, WillBeOwned - wantParRi c.dest, n elif isLastRead(c, ri): if isNotFirstAsgn and potentialSelfAsgn(le, ri): # `let tmp = y; =wasMoved(y); =destroy(x); x =bitcopy tmp` - let tmp = tempOfTrArg(c, dest, ri, riType) - callWasMoved c, dest, ri - callDestroy(c, dest, destructor, lhs) - copyInto dest, n: - #tr c, dest, lhs, DontCare - # XXX Fixme - copyTree dest, lhs - copyIntoSymUse dest, tmp, ri.info + let tmp = tempOfTrArg(c, ri, riType) + callWasMoved c, ri + callDestroy(c, destructor, lhs) + copyInto c.dest, n: + var lhsAsCursor = cursorAt(lhs, 0) + tr c, lhsAsCursor, DontCare + copyIntoSymUse c.dest, tmp, ri.info + n = n2 + skip n # skip right hand side else: if isNotFirstAsgn: - callDestroy(c, dest, destructor, lhs) - copyInto dest, n: - copyTree dest, lhs - tr c, dest, ri, WillBeOwned - callWasMoved c, dest, ri + callDestroy(c, destructor, lhs) + copyInto c.dest, n: + copyTree c.dest, lhs + var n = ri + tr c, n, WillBeOwned + callWasMoved c, ri else: if isNotFirstAsgn and potentialSelfAsgn(le, ri): # `let tmp = x; x =bitcopy =dup(y); =destroy(tmp)` - let tmp = tempOfTrArg(c, dest, lhs, riType) - copyInto dest, n: - tr c, dest, lhs, DontCare - callDup c, dest, ri - callDestroy(c, dest, destructor, tmp[le].info) + let tmp = tempOfTrArg(c, ri, riType) + copyInto c.dest, n: + var lhsAsCursor = cursorAt(lhs, 0) + tr c, lhsAsCursor, DontCare + var n = ri + callDup c, n + callDestroy(c, destructor, tmp, le.info) else: if isNotFirstAsgn: - callDestroy(c, dest, destructor, lhs) - copyInto dest, n: + callDestroy(c, destructor, lhs) + copyInto c.dest, n: #tr c, dest, lhs, DontCare # XXX Fixme - copyTree dest, lhs + copyTree c.dest, lhs #callDup c, dest, ri - tr c, dest, ri, WantOwner + tr c, ri, WantOwner proc trExplicitDestroy(c: var Context; n: Cursor) = @@ -504,7 +508,7 @@ proc trLocal(c: var Context; n: Cursor) = else: tr c, dest, r.value, WillBeOwned if wasMovedArg != noPos: - callWasMoved c, dest, wasMovedArg + callWasMoved c, wasMovedArg proc trStmtListExpr(c: var Context; n: Cursor; e: Expects) = let (t, s, x) = sons3(tree, n) From b0ac12537ef64b2ba60573904ca2c0afdbed4200 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 10 Jan 2025 19:05:15 +0100 Subject: [PATCH 04/10] progress --- src/gear3/duplifier.nim | 133 ++++++++++++++++++++++++++-------------- 1 file changed, 86 insertions(+), 47 deletions(-) diff --git a/src/gear3/duplifier.nim b/src/gear3/duplifier.nim index 3165888b..03066527 100644 --- a/src/gear3/duplifier.nim +++ b/src/gear3/duplifier.nim @@ -220,6 +220,8 @@ proc trAsgn(c: var Context; n: var Cursor) = or `let tmp = x; x =bitcopy =dup(y); =destroy(tmp)` # safe for self assignments + + But we should really prefer to call `=copy(x, y)` here for some of these cases. ]# var n2 = n inc n2 # AsgnS @@ -265,6 +267,7 @@ proc trAsgn(c: var Context; n: var Cursor) = tr c, n, WillBeOwned callWasMoved c, ri else: + # XXX We should really prefer to simply call `=copy(x, y)` here. if isNotFirstAsgn and potentialSelfAsgn(le, ri): # `let tmp = x; x =bitcopy =dup(y); =destroy(tmp)` let tmp = tempOfTrArg(c, ri, riType) @@ -278,86 +281,122 @@ proc trAsgn(c: var Context; n: var Cursor) = if isNotFirstAsgn: callDestroy(c, destructor, lhs) copyInto c.dest, n: - #tr c, dest, lhs, DontCare - # XXX Fixme - copyTree c.dest, lhs - #callDup c, dest, ri - tr c, ri, WantOwner + var lhsAsCursor = cursorAt(lhs, 0) + tr c, lhsAsCursor, DontCare + var n = ri + callDup c, n +proc skipParRi*(n: var Cursor) = + if n.kind == ParRi: + inc n + else: + error "expected ')', but got: ", n -proc trExplicitDestroy(c: var Context; n: Cursor) = - let typ = getType(c.p, n.firstSon) +proc trExplicitDestroy(c: var Context; n: var Cursor) = + let typ = getType(c.typeCache, n.firstSon) let info = n.info let destructor = getDestructor(c.lifter[], typ, info) if destructor == NoSymId: # the type has no destructor, there is nothing interesting to do: c.dest.addEmpty info + inc n + skip n else: copyIntoKind c.dest, CallS, info: copyIntoSymUse c.dest, destructor, info inc n tr c, n, DontCare - skipParRi n + skipParRi n -proc trExplicitDup(c: var Context; n: Cursor; e: Expects) = - let typ = getType(c.p, n) +proc trExplicitDup(c: var Context; n: var Cursor; e: Expects) = + let typ = getType(c.typeCache, n) let info = n.info let hookProc = getHook(c.lifter[], attachedDup, typ, info) - if hookProc.s != SymId(-1): - copyIntoKind dest, Call, info: - copyIntoSymUse c.p, dest, hookProc, info - tr c, dest, n.firstSon, DontCare + if hookProc != NoSymId: + copyIntoKind c.dest, CallS, info: + copyIntoSymUse c.dest, hookProc, info + inc n + tr c, n, DontCare else: let e2 = if e == WillBeOwned: WantOwner else: e - tr c, dest, n.firstSon, e2 + inc n + tr c, n, e2 + skipParRi n -proc trOnlyEssentials(c: var Context; n: Cursor) = - if isAtom(tree, n): - copyTree dest, n - else: +proc trOnlyEssentials(c: var Context; n: var Cursor) = + var nested = 0 + while true: case n.kind - of EqDestroy: trExplicitDestroy c, dest, n - of EqDup: trExplicitDup c, dest, n, DontCare - else: - for ch in sons(dest, n): trOnlyEssentials c, dest, ch + of Symbol, UIntLit, StringLit, IntLit, FloatLit, CharLit, SymbolDef, UnknownToken, EofToken, DotToken, Ident: + c.dest.add n + inc n + of ParLe: + var handled = false + if n.exprKind in CallKinds: + var n = firstSon n + if n.kind == Symbol: + case pool.syms[n.symId] + of "=dup": + trExplicitDup c, n, DontCare + handled = true + of "=destroy": + trExplicitDestroy c, n + handled = true + if not handled: + c.dest.add n + inc n + inc nested + of ParRi: + c.dest.add n + inc n + dec nested + if nested == 0: break -proc trProcDecl(c: var Context; n: Cursor) = - let r = asRoutine(tree, n) - var c2 = Context(p: c.p, lifter: c.lifter, localTypes: c.localTypes) - c2.reportLastUse = compilerShouldReport(c.p, r.pragmas, "lastUse") +proc hasBuiltinPragma(n: Cursor; kind: PragmaKind): bool = + result = false + var n = n + if n.kind == DotToken: + discard + else: + inc n + while n.kind != ParRi: + if pragmaKind(n) == kind: + result = true + break + skip n - copyInto(dest, n): - copyTree dest, r.name - copyTree dest, r.ex - copyTree dest, r.pat - copyTree dest, r.generics - copyTree dest, r.params - copyTree dest, r.pragmas - copyTree dest, r.exc - if r.body.kind == StmtList and r.generics.kind != GenericParams: - if hasBuiltinPragma(c.p, r.pragmas, "nodestroy"): - trOnlyEssentials c2, dest, r.body - else: - tr c2, dest, r.body, DontCare +proc trProcDecl(c: var Context; n: var Cursor) = + c.dest.add n + var r = takeRoutine(n) + copyTree c.dest, r.name + copyTree c.dest, r.exported + copyTree c.dest, r.pattern + copyTree c.dest, r.typevars + copyTree c.dest, r.params + copyTree c.dest, r.pragmas + copyTree c.dest, r.effects + if r.body.stmtKind == StmtsS and not isGeneric(r): + if hasBuiltinPragma(r.pragmas, NoDestroy): + trOnlyEssentials c, r.body else: - copyTree dest, r.body + tr c, r.body, DontCare + else: + copyTree c.dest, r.body + c.dest.addParRi() proc hasDestructor(c: Context; typ: Cursor): bool {.inline.} = - not isTrivial(c.lifter[], FullTypeId(t: typ, m: tree.id)) - -proc hasDestructor(c: Context; typ: FullTypeId): bool {.inline.} = not isTrivial(c.lifter[], typ) type OwningTemp = object - ex, st, vr: PatchPos + active: bool s: SymId info: PackedLineInfo template owningTempDefault(): OwningTemp = - OwningTemp(ex: PatchPos(-1), st: PatchPos(-1), vr: PatchPos(-1), s: SymId(-1), info: UnknownLineInfo) + OwningTemp(active: false, s: NoSymId, info: NoLineInfo) -proc bindToTemp(c: var Context; typ: FullTypeId; info: PackedLineInfo; kind = VarDecl): OwningTemp = +proc bindToTemp(c: var Context; typ: Cursor; info: PackedLineInfo; kind = VarDecl): OwningTemp = let s = declareSym(c.p[dest.m], kind, c.p[dest.m].strings.getOrIncl("temp")) let ex = prepare(dest, StmtListExpr, info) From 59abae58964265359dccfa51be5249c84007f6c6 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 10 Jan 2025 22:30:59 +0100 Subject: [PATCH 05/10] progress --- src/gear3/duplifier.nim | 93 +++++++++++++++++++++-------------------- src/nimony/derefs.nim | 1 - src/nimony/typenav.nim | 7 +++- 3 files changed, 53 insertions(+), 48 deletions(-) diff --git a/src/gear3/duplifier.nim b/src/gear3/duplifier.nim index 03066527..bc8a34e1 100644 --- a/src/gear3/duplifier.nim +++ b/src/gear3/duplifier.nim @@ -396,52 +396,64 @@ type template owningTempDefault(): OwningTemp = OwningTemp(active: false, s: NoSymId, info: NoLineInfo) -proc bindToTemp(c: var Context; typ: Cursor; info: PackedLineInfo; kind = VarDecl): OwningTemp = - let s = declareSym(c.p[dest.m], kind, c.p[dest.m].strings.getOrIncl("temp")) - - let ex = prepare(dest, StmtListExpr, info) - copyTreeX dest, c.p[typ.m], typ.t, c.p - let st = prepare(dest, StmtList, info) - let vr = prepare(dest, VarDecl, info) - addSymDef dest, s, info - dest.addEmpty2 info # export marker, pragmas - copyTreeX dest, c.p[typ.m], typ.t, c.p # type - # copyTree dest, ex # value +proc bindToTemp(c: var Context; typ: Cursor; info: PackedLineInfo; kind = VarS): OwningTemp = + let s = pool.syms.getOrIncl("`tmp." & $c.tmpCounter) + inc c.tmpCounter + + c.dest.addParLe ExprX, info + c.dest.addParLe StmtsS, info + + c.dest.addParLe kind, info + addSymDef c.dest, s, info + c.dest.addEmpty2 info # export marker, pragmas + copyTree c.dest, typ # value is filled in by the caller! - result = OwningTemp(ex: ex, st: st, vr: vr, s: s, info: info) + result = OwningTemp(active: true, s: s, info: info) -proc finishOwningTemp(dest: var Tree; ow: OwningTemp) = - if ow.s != SymId(-1): - dest.patch ow.vr # finish the VarDecl - dest.patch ow.st # finish the StmtList +proc finishOwningTemp(dest: var TokenBuf; ow: OwningTemp) = + if ow.active: + dest.addParRi() # finish the VarS + dest.addParRi() # finish the StmtsS dest.copyIntoSymUse ow.s, ow.info - dest.patch ow.ex # finish the StmtListExpr + dest.addParRi() # finish the StmtListExpr -proc trCall(c: var Context; n: Cursor; e: Expects) = +proc trCall(c: var Context; n: var Cursor; e: Expects) = var ow = owningTempDefault() - let retType = getType(c.p, n) + let retType = getType(c.typeCache, n) if hasDestructor(c, retType) and e == WantNonOwner: - ow = bindToTemp(c, dest, retType, n.info) - - var fnType = getType(c.p, n.firstSon) - fnType = skipGenericInsts(c.p, fnType, {SkipNilTy, SkipObjectInstantiations}) - assert c.p[fnType].kind in ProcTyNodes - var paramIter = initSonsIter(c.p, ithSon(c.p, fnType, routineParamsPos)) - var i = 0 - for ch in sons(dest, n): + ow = bindToTemp(c, retType, n.info) + + c.dest.add n + inc n # skip `(call)` + var fnType = getType(c.typeCache, n) + takeTree c.dest, n # skip `fn` + assert fnType == "params" + inc fnType + while n.kind != ParRi: + let previousFormalParam = fnType + let param = takeLocal(fnType) + let pk = param.typ.typeKind var e2 = WantNonOwner - if hasCurrent(paramIter): - if i > 0 and c.p[ithSon(c.p, paramIter.current, localTypePos)].kind == SinkTy: e2 = WantOwner - next paramIter, c.p - tr c, dest, ch, e2 - inc i + if pk == SinkT: + e2 = WantOwner + elif pk == VarargsT: + # do not advance formal parameter: + fnType = previousFormalParam + else: + skipParRi(fnType) + tr c, n, e2 + wantParRi c.dest, n finishOwningTemp dest, ow proc trRawConstructor(c: var Context; n: Cursor; e: Expects) = # Idioms like `echo ["ab", myvar, "xyz"]` are important to translate well. let e2 = if e == WillBeOwned: WantOwner else: e - for ch in sons(dest, n): - tr c, dest, ch, e2 + c.dest.add n + inc n + while n.kind != ParRi: + tr c, n, e2 + wantParRi c.dest, n + proc trConstructor(c: var Context; typ, ex: Cursor; e: Expects) = # Idioms like `echo ["ab", myvar, "xyz"]` are important to translate well. @@ -450,15 +462,6 @@ proc trConstructor(c: var Context; typ, ex: Cursor; e: Expects) = copyTree dest, typ for ch in sons(dest, ex): tr c, dest, ch, e2 - when false: - var ow = owningTempDefault() - if hasDestructor(c, typ) and e == WantNonOwner: - ow = bindToTemp(c, dest, FullTypeId(m: tree.id, t: typ), ex.info) - copyIntoKind dest, TypedExpr, ex.info: - copyTree dest, typ - for ch in sons(dest, ex): - tr c, dest, ch, WantOwner - finishOwningTemp dest, ow proc trConvExpr(c: var Context; n: Cursor; e: Expects) = let (typ, ex) = sons2(tree, n) @@ -489,8 +492,8 @@ proc genLastRead(c: var Context; n: Cursor; typ: FullTypeId) = dest.patch ow.vr # finish the VarDecl let hookProc = getHook(c.lifter[], attachedWasMoved, typ, info) - if hookProc.s != SymId(-1): - copyIntoKind dest, Call, info: + if hookProc != NoSymId: + copyIntoKind dest, CallS, info: copyIntoSymUse c.p, dest, hookProc, info copyIntoKind dest, HiddenAddr, info: copyTree dest, n diff --git a/src/nimony/derefs.nim b/src/nimony/derefs.nim index 18205205..c10b9c2a 100644 --- a/src/nimony/derefs.nim +++ b/src/nimony/derefs.nim @@ -249,7 +249,6 @@ proc checkForDangerousLocations(c: var Context; n: var Cursor) = # do not advance formal parameter: fnType = previousFormalParam else: - skip fnType # potential default value skipParRi(fnType) skip n n = orig diff --git a/src/nimony/typenav.nim b/src/nimony/typenav.nim index 83a9d204..1bcadf6f 100644 --- a/src/nimony/typenav.nim +++ b/src/nimony/typenav.nim @@ -37,8 +37,11 @@ proc getTypeImpl(c: var TypeCache; n: Cursor): Cursor = if local.kind.isLocal: result = local.typ else: - if isRoutine(symKind(res.decl)): - result = res.decl + let fn = asRoutine(res.decl) + if isRoutine(fn.kind): + result = fn.params + #if isRoutine(symKind(res.decl)): + # result = res.decl else: quit "could not find symbol: " & pool.syms[n.symId] of IntLit: From 5351f8ccbf59023f3a15ef0b7a0e7dd96c19716c Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 10 Jan 2025 23:01:48 +0100 Subject: [PATCH 06/10] progress --- src/gear3/duplifier.nim | 71 +++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/src/gear3/duplifier.nim b/src/gear3/duplifier.nim index bc8a34e1..18e4e511 100644 --- a/src/gear3/duplifier.nim +++ b/src/gear3/duplifier.nim @@ -335,7 +335,9 @@ proc trOnlyEssentials(c: var Context; n: var Cursor) = if n.exprKind in CallKinds: var n = firstSon n if n.kind == Symbol: - case pool.syms[n.symId] + var fn = pool.syms[n.symId] + extractBasename fn + case fn of "=dup": trExplicitDup c, n, DontCare handled = true @@ -443,9 +445,9 @@ proc trCall(c: var Context; n: var Cursor; e: Expects) = skipParRi(fnType) tr c, n, e2 wantParRi c.dest, n - finishOwningTemp dest, ow + finishOwningTemp c.dest, ow -proc trRawConstructor(c: var Context; n: Cursor; e: Expects) = +proc trRawConstructor(c: var Context; n: var Cursor; e: Expects) = # Idioms like `echo ["ab", myvar, "xyz"]` are important to translate well. let e2 = if e == WillBeOwned: WantOwner else: e c.dest.add n @@ -454,53 +456,44 @@ proc trRawConstructor(c: var Context; n: Cursor; e: Expects) = tr c, n, e2 wantParRi c.dest, n +proc trConvExpr(c: var Context; n: var Cursor; e: Expects) = + copyInto c.dest, n: + takeTree c.dest, n # type + tr c, n, e -proc trConstructor(c: var Context; typ, ex: Cursor; e: Expects) = - # Idioms like `echo ["ab", myvar, "xyz"]` are important to translate well. - let e2 = if e == WillBeOwned: WantOwner else: e - copyIntoKind dest, TypedExpr, ex.info: - copyTree dest, typ - for ch in sons(dest, ex): - tr c, dest, ch, e2 - -proc trConvExpr(c: var Context; n: Cursor; e: Expects) = - let (typ, ex) = sons2(tree, n) - copyInto dest, n: - copyTree dest, typ - tr c, dest, ex, e - -proc trObjConstr(c: var Context; n: Cursor; e: Expects) = +proc trObjConstr(c: var Context; n: var Cursor; e: Expects) = var ow = owningTempDefault() let typ = n.firstSon if hasDestructor(c, typ) and e == WantNonOwner: - ow = bindToTemp(c, dest, FullTypeId(m: tree.id, t: typ), n.info) - copyIntoKind dest, ObjConstr, n.info: - copyTree dest, typ - for ch in sonsFrom1(tree, n): - let (first, second) = sons2(tree, ch) - copyIntoKind dest, ch.kind, ch.info: - copyTree dest, first - tr c, dest, second, WantOwner - finishOwningTemp dest, ow - -proc genLastRead(c: var Context; n: Cursor; typ: FullTypeId) = + ow = bindToTemp(c, typ, n.info) + copyInto c.dest, n: + takeTree c.dest, n + while n.kind != ParRi: + assert n.exprKind == KvX + copyInto c.dest, n: + takeTree c.dest, n + tr c, n, WantOwner + finishOwningTemp c.dest, ow + +proc genLastRead(c: var Context; n: var Cursor; typ: Cursor) = + let ex = n let info = n.info # translate it to: `(var tmp = location; wasMoved(location); tmp)` - var ow = bindToTemp(c, dest, typ, info, CursorDecl) - copyTree dest, n + var ow = bindToTemp(c, typ, info, CursorS) + takeTree c.dest, n - dest.patch ow.vr # finish the VarDecl + c.dest.addParRi() # finish the VarDecl let hookProc = getHook(c.lifter[], attachedWasMoved, typ, info) if hookProc != NoSymId: - copyIntoKind dest, CallS, info: - copyIntoSymUse c.p, dest, hookProc, info - copyIntoKind dest, HiddenAddr, info: - copyTree dest, n + copyIntoKind c.dest, CallS, info: + copyIntoSymUse c.dest, hookProc, info + copyIntoKind c.dest, HaddrX, info: + copyTree c.dest, ex - dest.patch ow.st # finish the StmtList - dest.copyIntoSymUse ow.s, ow.info - dest.patch ow.ex # finish the StmtListExpr + c.dest.addParRi() # finish the StmtList + c.dest.copyIntoSymUse ow.s, ow.info + c.dest.addParRi() # finish the StmtListExpr proc trLocation(c: var Context; n: Cursor; e: Expects) = # `x` does not own its value as it can be read multiple times. From 02177e7f6b46840db6f20b9105bba8de3293db05 Mon Sep 17 00:00:00 2001 From: Araq Date: Sat, 11 Jan 2025 10:25:30 +0100 Subject: [PATCH 07/10] duplifier compiles --- src/gear3/duplifier.nim | 244 +++++++++++++++++++----------------- src/nimony/derefs.nim | 8 +- src/nimony/nimony_model.nim | 1 + 3 files changed, 134 insertions(+), 119 deletions(-) diff --git a/src/gear3/duplifier.nim b/src/gear3/duplifier.nim index 18e4e511..e5e39983 100644 --- a/src/gear3/duplifier.nim +++ b/src/gear3/duplifier.nim @@ -55,7 +55,8 @@ proc isLastRead(c: Context; n: Cursor): bool = false const - ConstructingExprs = {CallX, CallStrLitX, InfixX, PrefixX, CmdX, OconstrX, AconstrX, TupleConstrX} + ConstructingExprs = {CallX, CallStrLitX, InfixX, PrefixX, CmdX, OconstrX, + AconstrX, TupleConstrX} proc constructsValue*(n: Cursor): bool = var n = n @@ -72,7 +73,7 @@ proc constructsValue*(n: Cursor): bool = proc rootOf(n: Cursor): SymId = var n = n - while n.exprKind in {DotX, TupAtX, AtX}: + while n.exprKind in {DotX, TupAtX, AtX, ArrAtX}: n = n.firstSon if n.kind == Symbol: result = n.symId @@ -105,9 +106,20 @@ proc potentialSelfAsgn(dest, src: Cursor): bool = when not defined(nimony): proc tr(c: var Context; n: var Cursor; e: Expects) +proc wantParRi(dest: var TokenBuf; n: var Cursor) = + if n.kind == ParRi: + dest.add n + inc n + else: + error "expected ')', but got: ", n + proc trSons(c: var Context; n: var Cursor; e: Expects) = + assert n.kind == ParLe + c.dest.add n + inc n while n.kind != ParRi: - tr c, n, e + tr(c, n, e) + wantParRi c.dest, n proc isResultUsage(n: Cursor): bool {.inline.} = result = false @@ -117,13 +129,6 @@ proc isResultUsage(n: Cursor): bool {.inline.} = let r = asLocal(res.decl) result = r.kind == ResultY -proc wantParRi(dest: var TokenBuf; n: var Cursor) = - if n.kind == ParRi: - dest.add n - inc n - else: - error "expected ')', but got: ", n - template copyInto*(dest: var TokenBuf; n: var Cursor; body: untyped) = assert n.kind == ParLe dest.add n @@ -495,131 +500,140 @@ proc genLastRead(c: var Context; n: var Cursor; typ: Cursor) = c.dest.copyIntoSymUse ow.s, ow.info c.dest.addParRi() # finish the StmtListExpr -proc trLocation(c: var Context; n: Cursor; e: Expects) = +proc isAtom(n: Cursor): bool {.inline.} = n.kind >= ParLe + +proc trLocation(c: var Context; n: var Cursor; e: Expects) = # `x` does not own its value as it can be read multiple times. - let typ = getType(c.p, n) + let typ = getType(c.typeCache, n) if e == WantOwner and hasDestructor(c, typ): if isLastRead(c, n): - genLastRead(c, dest, n, typ) + genLastRead(c, n, typ) else: let info = n.info # translate `x` to `=dup(x)`: let hookProc = getHook(c.lifter[], attachedDup, typ, info) - if hookProc.s != SymId(-1): - copyIntoKind dest, Call, info: - copyIntoSymUse c.p, dest, hookProc, info - if isAtom(tree, n): - copyTree dest, n + if hookProc != NoSymId: + copyIntoKind c.dest, CallS, info: + copyIntoSymUse c.dest, hookProc, info + if isAtom(n): + takeTree c.dest, n else: - trSons c, dest, n, DontCare - elif isAtom(tree, n): - copyTree dest, n + trSons c, n, DontCare + elif isAtom(n): + takeTree c.dest, n else: - trSons c, dest, n, DontCare - elif isAtom(tree, n): - copyTree dest, n + trSons c, n, DontCare + elif isAtom(n): + takeTree c.dest, n else: - trSons c, dest, n, DontCare - -proc trLocal(c: var Context; n: Cursor) = - let r = asLocal(tree, n) - var wasMovedArg = noPos - copyInto(dest, n): - copyTree dest, r.name - copyTree dest, r.ex - copyTree dest, r.pragmas - copyTree dest, r.typ - - let localType = getType(c.p, r.name) - let destructor = getDestructor(c.lifter[], localType, n.info) - if destructor.s != SymId(-1): - if constructsValue(c.p, r.value): - tr c, dest, r.value, WillBeOwned - elif isLastRead(c, r.value): - tr c, dest, r.value, WillBeOwned - wasMovedArg = r.value - else: - callDup c, dest, r.value + trSons c, n, DontCare + +proc trLocal(c: var Context; n: var Cursor) = + c.dest.add n + var r = takeLocal(n) + copyTree c.dest, r.name + copyTree c.dest, r.exported + copyTree c.dest, r.pragmas + copyTree c.dest, r.typ + + let destructor = getDestructor(c.lifter[], r.typ, n.info) + if destructor != NoSymId: + if constructsValue(r.val): + tr c, r.val, WillBeOwned + c.dest.addParRi() + + elif isLastRead(c, r.val): + tr c, r.val, WillBeOwned + c.dest.addParRi() + callWasMoved c, r.val + else: + callDup c, r.val + c.dest.addParRi() + else: + tr c, r.val, WillBeOwned + c.dest.addParRi() + +proc trStmtListExpr(c: var Context; n: var Cursor; e: Expects) = + c.dest.add n + inc n + while n.kind != ParRi: + if isLastSon(n): + tr(c, n, e) else: - tr c, dest, r.value, WillBeOwned - if wasMovedArg != noPos: - callWasMoved c, wasMovedArg - -proc trStmtListExpr(c: var Context; n: Cursor; e: Expects) = - let (t, s, x) = sons3(tree, n) - copyInto dest, n: - copyTree dest, t - tr(c, dest, s, WantNonOwner) - tr(c, dest, x, e) - -proc trEnsureMove(c: var Context; n: Cursor; e: Expects) = - let typ = getType(c.p, n) - if isLastRead(c, n.firstSon): + tr(c, n, WantNonOwner) + wantParRi c.dest, n + +proc trEnsureMove(c: var Context; n: var Cursor; e: Expects) = + let typ = getType(c.typeCache, n) + let arg = n.firstSon + let info = n.info + if isLastRead(c, arg): if e == WantOwner and hasDestructor(c, typ): - copyInto dest, n: - genLastRead(c, dest, n, typ) + copyInto c.dest, n: + genLastRead(c, n, typ) else: - copyInto dest, n: - tr c, dest, n.firstSon, e - elif constructsValue(c.p, n.firstSon): + copyInto c.dest, n: + tr c, n, e + elif constructsValue(arg): # we allow rather silly code like `ensureMove(234)`. # Seems very useful for generic programming as this can come up # from template expansions: - copyInto dest, n: - tr c, dest, n.firstSon, e + copyInto c.dest, n: + tr c, n, e else: - produceError dest, n, c.p[tree.m], ["not the last usage of: $1"] - -proc tr(c: var Context; n: Cursor; e: Expects) = - case n.kind - of Call: - trCall c, dest, n, e - of TypedExpr: - let (typ, ex) = sons2(tree, n) - if constructsValue(c.p, n): - trConstructor c, dest, typ, ex, e - else: - trSons c, dest, n, DontCare - of ConvExpr, CastExpr: - trConvExpr c, dest, n, e - of ObjConstr: - trObjConstr c, dest, n, e - of SymUse, ModuleSymUse, FieldAccess, VarFieldAccess, TupleFieldAccess, PtrFieldAccess, - PtrVarFieldAccess, RefFieldAccess, RefVarFieldAccess, ArrayAt, ArrayPtrAt: - trLocation c, dest, n, e - of ReturnStmt: - trReturn(c, dest, n) - of Asgn, FirstAsgn: - trAsgn c, dest, n - of VarDecl, LetDecl, ConstDecl, ResultDecl: - trLocal c, dest, n - of DeclarativeNodes, AtomsExceptSymUse, Pragmas, TemplateDecl, IteratorDecl, - UsingStmt, CommentStmt, BindStmt, MixinStmt, ContinueStmt, BreakStmt: - copyTree dest, n - of ProcDecl, FuncDecl, MacroDecl, MethodDecl, ConverterDecl: - trProcDecl c, dest, n - of Par: - trSons(c, dest, n, e) - of StmtListExpr: - trStmtListExpr c, dest, n, e - of TupleConstr, BracketConstr: - trRawConstructor c, dest, n, e - of EqDup: - trExplicitDup c, dest, n, e - of EqDestroy: - trExplicitDestroy c, dest, n - of EnsureMove: - trEnsureMove c, dest, n, e + let m = "not the last usage of: " & toString(n, false) + c.dest.buildTree ErrT, info: + c.dest.add strToken(pool.strings.getOrIncl(m), info) + +proc tr(c: var Context; n: var Cursor; e: Expects) = + if n.kind == Symbol: + trLocation c, n, e + elif n.kind in {Ident, SymbolDef, IntLit, UIntLit, CharLit, StringLit} or isDeclarative(n): + takeTree c.dest, n else: - takeToken c.dest, n - while n.kind != ParRi: - tr(c, n, WantNonOwner) - wantParRi c.dest, n + case n.exprKind + of CallKinds: + trCall c, n, e + of ConvKinds, SufX: + trConvExpr c, n, e + of OconstrX: + trObjConstr c, n, e + of DotX, AtX, ArrAtX, PatX, TupAtX: + trLocation c, n, e + of ParX: + trSons c, n, e + of ExprX: + trStmtListExpr c, n, e + of EnsureMoveX: + trEnsureMove c, n, e + of AconstrX, TupleConstrX: + trRawConstructor c, n, e + of NilX, FalseX, TrueX, AndX, OrX, NotX, NegX, SizeofX, SetX, + OchoiceX, CchoiceX, KvX, + AddX, SubX, MulX, DivX, ModX, ShrX, ShlX, AshrX, BitandX, BitorX, BitxorX, BitnotX, + EqX, NeqX, LeX, LtX, InfX, NegInfX, NanX, RangeX, RangesX, CompilesX, DeclaredX, + DefinedX, HighX, LowX, TypeofX, UnpackX, EnumToStrX, IsMainModuleX, QuotedX, + DerefX, HderefX, AddrX, HaddrX: + trSons c, n, WantNonOwner + of DefaultObjX, DefaultTupX: + raiseAssert "nodekind should have been eliminated in sem.nim" + of NoExpr: + case n.stmtKind + of RetS: + trReturn c, n + of AsgnS: + trAsgn c, n + of VarS, LetS, ConstS, ResultS, CursorS: + trLocal c, n + of ProcS, FuncS, ConverterS, MethodS, MacroS: + trProcDecl c, n + else: + trSons c, n, WantNonOwner proc injectDups*(n: Cursor; lifter: ref LiftingCtx): TokenBuf = - var c = Context(lifter: lifter, typeCache: createTypeCache()) - - result = createTree(p, thisModule) + var c = Context(lifter: lifter, typeCache: createTypeCache(), + dest: createTokenBuf(400)) + var n = n tr(c, n, WantNonOwner) genMissingHooks lifter[] diff --git a/src/nimony/derefs.nim b/src/nimony/derefs.nim index c10b9c2a..4937d99b 100644 --- a/src/nimony/derefs.nim +++ b/src/nimony/derefs.nim @@ -61,7 +61,7 @@ proc rootOf(n: Cursor): SymId = var n = n while true: case n.exprKind - of DotX, AtX, ParX: + of DotX, AtX, ArrAtX, ParX: # `PatX` deliberately missing here as it is not protected from mutation inc n of DconvX, OconvX: @@ -102,7 +102,7 @@ proc validBorrowsFrom(c: var Context; n: Cursor): bool = var someIndirection = false while true: case n.exprKind - of DotX, AtX, ParX: + of DotX, AtX, ArrAtX, ParX: inc n of HderefX, HaddrX, DerefX, AddrX: inc n @@ -154,7 +154,7 @@ proc borrowsFromReadonly(c: var Context; n: Cursor): bool = var n = n while true: case n.exprKind - of DotX, AtX, ParX: + of DotX, AtX, ArrAtX, ParX: inc n of DconvX, HconvX, ConvX, CastX: inc n @@ -470,7 +470,7 @@ proc tr(c: var Context; n: var Cursor; e: Expects) = of CallKinds: var disallowDangerous = true trCall c, n, e, disallowDangerous - of DotX, AtX, PatX: + of DotX, AtX, ArrAtX, PatX: trLocation c, n, e of OconstrX: if e notin {WantT, WantTButSkipDeref}: diff --git a/src/nimony/nimony_model.nim b/src/nimony/nimony_model.nim index fca65835..80ad7e0a 100644 --- a/src/nimony/nimony_model.nim +++ b/src/nimony/nimony_model.nim @@ -320,6 +320,7 @@ template `==`*(n: Cursor; s: string): bool = n.kind == ParLe and pool.tags[n.tag const RoutineKinds* = {ProcY, FuncY, IterY, TemplateY, MacroY, ConverterY, MethodY} CallKinds* = {CallX, CallStrLitX, CmdX, PrefixX, InfixX} + ConvKinds* = {HconvX, ConvX, OconvX, DconvX, CastX} proc addParLe*(dest: var TokenBuf; kind: TypeKind|SymKind|ExprKind|StmtKind|SubstructureKind; info = NoLineInfo) = dest.add parLeToken(pool.tags.getOrIncl($kind), info) From a3bc120e12bf0812819218d05f0e5dfa6cc36e75 Mon Sep 17 00:00:00 2001 From: Araq Date: Sat, 11 Jan 2025 10:52:27 +0100 Subject: [PATCH 08/10] progress --- src/gear3/destroyer.nim | 211 ++++++++++++++++++++-------------------- src/gear3/gear3.nim | 2 +- 2 files changed, 106 insertions(+), 107 deletions(-) diff --git a/src/gear3/destroyer.nim b/src/gear3/destroyer.nim index 17b47870..b6a03820 100644 --- a/src/gear3/destroyer.nim +++ b/src/gear3/destroyer.nim @@ -43,14 +43,14 @@ interprets this `=` as `=bitcopy`. ]## +import std / assertions include nifprelude import nifindexes, symparser, treemangler import ".." / nimony / [nimony_model, programs, typenav] import lifter const - NoLabel = SymId(-2) - AnonBlock = SymId(-3) + NoLabel = SymId(0) type ScopeKind = enum @@ -61,38 +61,41 @@ type arg: SymId Scope = object - p: Program label: SymId - thisModule: ModuleId - procStart: Cursor kind: ScopeKind destroyOps: seq[DestructorOp] info: PackedLineInfo parent: ptr Scope - lifter: ref LiftingCtx isTopLevel: bool + Context = object + currentScope: Scope + #procStart: Cursor + anonBlock: SymId + dest: TokenBuf + lifter: ref LiftingCtx + proc createNestedScope(kind: ScopeKind; parent: var Scope; info: PackedLineInfo; label = NoLabel): Scope = - Scope(p: parent.p, label: label, thisModule: parent.thisModule, procStart: parent.procStart, - kind: kind, destroyOps: @[], info: info, parent: addr(parent), lifter: parent.lifter, + Scope(label: label, + kind: kind, destroyOps: @[], info: info, parent: addr(parent), isTopLevel: false) -proc createEntryScope(p: Program; thisModule: ModuleId; lifter: ref LiftingCtx; - procStart: Cursor; info: PackedLineInfo): Scope = - Scope(p: p, label: NoLabel, thisModule: thisModule, procStart: procStart, - kind: Other, destroyOps: @[], info: info, parent: nil, lifter: lifter, +proc createEntryScope(info: PackedLineInfo): Scope = + Scope(label: NoLabel, + kind: Other, destroyOps: @[], info: info, parent: nil, isTopLevel: true) -proc callDestroy(c: var Scope; dest: var TokenBuf; destroyProc: SymId; arg: SymId) = - copyIntoKind dest, Call, c.info: - copyIntoSymUse dest, destroyProc, c.info - copyIntoSymUse dest, arg, c.info +proc callDestroy(c: var Context; destroyProc: SymId; arg: SymId) = + let info = c.currentScope.info + copyIntoKind c.dest, CallS, info: + copyIntoSymUse c.dest, destroyProc, info + copyIntoSymUse c.dest, arg, info -proc leaveScope(c: var Scope; dest: var TokenBuf) = - for i in countdown(c.destroyOps.high, 0): - callDestroy c, dest, c.destroyOps[i].destroyProc, c.destroyOps[i].arg +proc leaveScope(c: var Context; s: var Scope) = + for i in countdown(s.destroyOps.high, 0): + callDestroy c, s.destroyOps[i].destroyProc, s.destroyOps[i].arg -proc leaveNamedBlock(c: var Scope; dest: var TokenBuf; label: SymId) = +proc leaveNamedBlock(c: var Context; label: SymId) = #[ Consider: var x = f() @@ -100,51 +103,51 @@ proc leaveNamedBlock(c: var Scope; dest: var TokenBuf; label: SymId) = break # do we want to destroy x here? No. ]# - var it = addr(c) + var it = addr(c.currentScope) while it != nil and it.label != label: - leaveScope(it[], dest) + leaveScope(c, it[]) it = it.parent if it != nil and it.label == label: - leaveScope(it[], dest) + leaveScope(c, it[]) else: - assert false, "do not know which block to leave" + raiseAssert "do not know which block to leave" -proc leaveAnonBlock(c: var Scope; dest: var TokenBuf) = - var it = addr(c) +proc leaveAnonBlock(c: var Context) = + var it = addr(c.currentScope) while it != nil and it.kind != WhileOrBlock: - leaveScope(it[], dest) + leaveScope(c, it[]) it = it.parent if it != nil and it.kind == WhileOrBlock: - leaveScope(it[], dest) + leaveScope(c, it[]) else: - assert false, "do not know which block to leave" + raiseAssert "do not know which block to leave" -proc trBreak(c: var Scope; dest: var TokenBuf; n: Cursor) = +proc trBreak(c: var Context; n: var Cursor) = let lab = n.firstSon - if lab.kind == SymUse: - leaveNamedBlock(c, dest, lab.symId) + if lab.kind == Symbol: + leaveNamedBlock(c, lab.symId) else: - leaveAnonBlock(c, dest) - copyTree dest, tree, n + leaveAnonBlock(c) + takeTree c.dest, n -proc trReturn(c: var Scope; dest: var TokenBuf; n: Cursor) = - var it = addr(c) +proc trReturn(c: var Context; n: var Cursor) = + var it = addr(c.currentScope) while it != nil: - leaveScope(it[], dest) + leaveScope(c, it[]) it = it.parent - copyTree dest, tree, n + takeTree c.dest, n when not defined(nimony): - proc tr(c: var Scope; dest: var TokenBuf; n: Cursor) + proc tr(c: var Context; n: var Cursor) -proc trLocal(c: var Scope; dest: var TokenBuf; n: Cursor) = +proc trLocal(c: var Context; n: Cursor) = let r = asLocal(tree, n) copyIntoKind(dest, n.kind, n.info): - copyTree(dest, tree, r.name) - copyTree(dest, tree, r.ex) - copyTree(dest, tree, r.pragmas) - copyTree(dest, tree, r.typ) - let localType = getType(c.p, tree, r.name) + copyTree(dest, r.name) + copyTree(dest, r.ex) + copyTree(dest, r.pragmas) + copyTree(dest, r.typ) + let localType = getType(c.p, r.name) let destructor = getDestructor(c.lifter[], localType, n.info) let s = r.name.symId let sk = c.p[tree.m].syms[s].kind @@ -152,15 +155,15 @@ proc trLocal(c: var Scope; dest: var TokenBuf; n: Cursor) = if not c.isTopLevel and sk != ResultDecl: # XXX If we don't free global variables let's at least free temporaries! c.destroyOps.add DestructorOp(destroyProc: destructor, arg: s) - tr c, dest, tree, r.value + tr c, dest, r.value -proc trScope(c: var Scope; dest: var TokenBuf; body: Cursor) = +proc trScope(c: var Context; body: Cursor) = copyIntoKind dest, StmtList, body.info: if body.kind == StmtList: for ch in sonsReadOnly(tree, body): - tr c, dest, tree, ch + tr c, dest, ch else: - tr c, dest, tree, body + tr c, dest, body leaveScope(c, dest) proc registerSinkParameters(c: var Scope; params: Cursor) = @@ -171,30 +174,30 @@ proc registerSinkParameters(c: var Scope; params: Cursor) = if destructor.s != SymId(-1): c.destroyOps.add DestructorOp(destroyProc: destructor, arg: r.name.symId) -proc trProcDecl(c: var Scope; dest: var TokenBuf; n: Cursor) = +proc trProcDecl(c: var Context; n: Cursor) = let r = asRoutine(tree, n) var c2 = createEntryScope(c.p, c.thisModule, c.lifter, r.body, r.body.info) c2.isTopLevel = false copyInto(dest, n): - copyTree dest, tree, r.name - copyTree dest, tree, r.ex - copyTree dest, tree, r.pat - copyTree dest, tree, r.generics - copyTree dest, tree, r.params - copyTree dest, tree, r.pragmas - copyTree dest, tree, r.exc + copyTree dest, r.name + copyTree dest, r.ex + copyTree dest, r.pat + copyTree dest, r.generics + copyTree dest, r.params + copyTree dest, r.pragmas + copyTree dest, r.exc if r.body.kind == StmtList and r.generics.kind != GenericParams and - not hasBuiltinPragma(c.p, tree, r.pragmas, "nodestroy"): - registerSinkParameters(c2, tree, r.params) - trScope c2, dest, tree, r.body + not hasBuiltinPragma(c.p, r.pragmas, "nodestroy"): + registerSinkParameters(c2, r.params) + trScope c2, dest, r.body else: - copyTree dest, tree, r.body + copyTree dest, r.body -proc trNestedScope(c: var Scope; dest: var TokenBuf; body: Cursor; kind = Other) = +proc trNestedScope(c: var Context; body: Cursor; kind = Other) = var bodyScope = createNestedScope(kind, c, body.info) - trScope bodyScope, dest, tree, body + trScope bodyScope, dest, body -proc trWhile(c: var Scope; dest: var TokenBuf; n: Cursor) = +proc trWhile(c: var Context; n: Cursor) = #[ while prop(createsObj()) was turned into `while (let tmp = createsObj(); prop(tmp))` by `duplifier.nim` already and `to_stmts` did turn it into: @@ -208,84 +211,80 @@ proc trWhile(c: var Scope; dest: var TokenBuf; n: Cursor) = ]# let (cond, body) = sons2(tree, n) copyInto(dest, n): - tr c, dest, tree, cond - trNestedScope c, dest, tree, body, WhileOrBlock + tr c, dest, cond + trNestedScope c, dest, body, WhileOrBlock -proc trBlock(c: var Scope; dest: var TokenBuf; n: Cursor) = +proc trBlock(c: var Context; n: Cursor) = let (label, body) = sons2(tree, n) let labelId = if label.kind == SymDef: label.symId else: AnonBlock var bodyScope = createNestedScope(WhileOrBlock, c, body.info, labelId) copyInto(dest, n): - copyTree dest, tree, label - trScope bodyScope, dest, tree, body + copyTree dest, label + trScope bodyScope, dest, body -proc trIf(c: var Scope; dest: var TokenBuf; n: Cursor) = - for ch in sons(dest, tree, n): +proc trIf(c: var Context; n: Cursor) = + for ch in sons(dest, n): case ch.kind of ElifBranch: let (cond, action) = sons2(tree, ch) copyInto(dest, ch): - tr c, dest, tree, cond - trNestedScope c, dest, tree, action + tr c, dest, cond + trNestedScope c, dest, action of ElseBranch: copyInto(dest, ch): - trNestedScope c, dest, tree, ch.firstSon + trNestedScope c, dest, ch.firstSon else: - copyTree dest, tree, ch + copyTree dest, ch -proc trCase(c: var Scope; dest: var TokenBuf; n: Cursor) = +proc trCase(c: var Context; n: Cursor) = copyInto(dest, n): - tr c, dest, tree, n.firstSon + tr c, dest, n.firstSon for ch in sonsFrom1(tree, n): case ch.kind of OfBranch: copyInto(dest, ch): let (first, action) = sons2(tree, ch) - copyTree dest, tree, first - trNestedScope c, dest, tree, action + copyTree dest, first + trNestedScope c, dest, action of ElseBranch: copyInto(dest, ch): - trNestedScope c, dest, tree, ch.firstSon + trNestedScope c, dest, ch.firstSon else: - copyTree dest, tree, ch + copyTree dest, ch -proc tr(c: var Scope; dest: var TokenBuf; n: Cursor) = +proc tr(c: var Context; n: var Cursor) = case n.kind of ReturnStmt: - trReturn(c, dest, tree, n) + trReturn(c, dest, n) of BreakStmt: - trBreak(c, dest, tree, n) + trBreak(c, dest, n) of IfStmt: - trIf c, dest, tree, n + trIf c, dest, n of CaseStmt: - trCase c, dest, tree, n + trCase c, dest, n of BlockStmt: - trBlock c, dest, tree, n + trBlock c, dest, n #of Asgn, FirstAsgn: - # trAsgn c, dest, tree, n + # trAsgn c, dest, n of VarDecl, LetDecl, ConstDecl, ResultDecl: - trLocal c, dest, tree, n + trLocal c, dest, n of WhileStmt: - trWhile c, dest, tree, n + trWhile c, dest, n of DeclarativeNodes, Atoms, Pragmas, TemplateDecl, IteratorDecl, UsingStmt, CommentStmt, BindStmt, MixinStmt, ContinueStmt: - copyTree dest, tree, n + copyTree dest, n of ProcDecl, FuncDecl, MacroDecl, MethodDecl, ConverterDecl: - trProcDecl c, dest, tree, n + trProcDecl c, dest, n else: - for ch in sons(dest, tree, n): - tr(c, dest, tree, ch) - -proc injectDestructors*(p: Program; t: TreeId; lifter: ref LiftingCtx): TreeId = - let thisModule = p[t].m - let info = p[t][StartPos].info - - var c = createEntryScope(p, thisModule, lifter, StartPos, info) - result = createTree(p, c.thisModule) - p[result].flags.incl dontTouch - - tr(c, p[result], p[t], StartPos) + for ch in sons(dest, n): + tr(c, dest, ch) + +proc injectDestructors*(n: Cursor; lifter: ref LiftingCtx): TokenBuf = + var c = Context(currentScope: createEntryScope(p, thisModule, lifter, StartPos, info), + anonBlock: pool.syms.getOrIncl("`anonblock.0"), + dest: createTokenBuf(400)) + var n = n + tr(c, n) leaveScope c, p[result] genMissingHooks lifter[], p[result] - patch p[result], PatchPos(0) - p[result].flags.excl dontTouch + result = ensureMove c.dest diff --git a/src/gear3/gear3.nim b/src/gear3/gear3.nim index ec173ef4..39fbf418 100644 --- a/src/gear3/gear3.nim +++ b/src/gear3/gear3.nim @@ -30,7 +30,7 @@ Gear 3 accepts Gear 2's grammar. ]## import std / [parseopt, strutils, os, osproc, tables, assertions, syncio] -import expander, lifter, duplifier #, destroyer +import expander, lifter, duplifier, destroyer const Version = "0.2" From e7979e196413f76c50cf4e75f05fce9898f10019 Mon Sep 17 00:00:00 2001 From: Araq Date: Sat, 11 Jan 2025 11:57:52 +0100 Subject: [PATCH 09/10] progress --- src/gear3/destroyer.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gear3/destroyer.nim b/src/gear3/destroyer.nim index b6a03820..e99d0795 100644 --- a/src/gear3/destroyer.nim +++ b/src/gear3/destroyer.nim @@ -46,7 +46,7 @@ interprets this `=` as `=bitcopy`. import std / assertions include nifprelude import nifindexes, symparser, treemangler -import ".." / nimony / [nimony_model, programs, typenav] +import ".." / nimony / [nimony_model, programs, typenav, decls] import lifter const @@ -141,7 +141,7 @@ when not defined(nimony): proc tr(c: var Context; n: var Cursor) proc trLocal(c: var Context; n: Cursor) = - let r = asLocal(tree, n) + let r = asLocal(n) copyIntoKind(dest, n.kind, n.info): copyTree(dest, r.name) copyTree(dest, r.ex) From 84f7e208b3858dd363972aae37982eb583986bc0 Mon Sep 17 00:00:00 2001 From: Araq Date: Sat, 11 Jan 2025 15:46:37 +0100 Subject: [PATCH 10/10] destroyer compiles --- src/gear3/destroyer.nim | 270 ++++++++++++++++++++---------------- src/gear3/duplifier.nim | 22 --- src/nimony/nimony_model.nim | 22 +++ 3 files changed, 169 insertions(+), 145 deletions(-) diff --git a/src/gear3/destroyer.nim b/src/gear3/destroyer.nim index e99d0795..660ca951 100644 --- a/src/gear3/destroyer.nim +++ b/src/gear3/destroyer.nim @@ -140,64 +140,83 @@ proc trReturn(c: var Context; n: var Cursor) = when not defined(nimony): proc tr(c: var Context; n: var Cursor) -proc trLocal(c: var Context; n: Cursor) = - let r = asLocal(n) - copyIntoKind(dest, n.kind, n.info): - copyTree(dest, r.name) - copyTree(dest, r.ex) - copyTree(dest, r.pragmas) - copyTree(dest, r.typ) - let localType = getType(c.p, r.name) - let destructor = getDestructor(c.lifter[], localType, n.info) - let s = r.name.symId - let sk = c.p[tree.m].syms[s].kind - if destructor.s != SymId(-1) and sk != CursorDecl: - if not c.isTopLevel and sk != ResultDecl: - # XXX If we don't free global variables let's at least free temporaries! - c.destroyOps.add DestructorOp(destroyProc: destructor, arg: s) - tr c, dest, r.value - -proc trScope(c: var Context; body: Cursor) = - copyIntoKind dest, StmtList, body.info: - if body.kind == StmtList: - for ch in sonsReadOnly(tree, body): - tr c, dest, ch +proc trLocal(c: var Context; n: var Cursor) = + let info = n.info + c.dest.add n + var r = takeLocal(n) + copyTree c.dest, r.name + copyTree c.dest, r.exported + copyTree c.dest, r.pragmas + copyTree c.dest, r.typ + + tr c, r.val + c.dest.addParRi() + + let destructor = getDestructor(c.lifter[], r.typ, info) + if destructor != NoSymId and r.kind notin {CursorY, ResultY} and not c.currentScope.isTopLevel: + # XXX If we don't free global variables let's at least free temporaries! + c.currentScope.destroyOps.add DestructorOp(destroyProc: destructor, arg: r.name.symId) + +proc trScope(c: var Context; body: var Cursor) = + copyIntoKind c.dest, StmtsS, body.info: + if body.stmtKind == StmtsS: + inc body + while body.kind != ParRi: + tr c, body + c.dest.addParRi() + inc body else: - tr c, dest, body - leaveScope(c, dest) - -proc registerSinkParameters(c: var Scope; params: Cursor) = - for ch in sonsFrom1(tree, params): - let r = asLocal(tree, ch) - if r.typ.kind == SinkTy: - let destructor = getDestructor(c.lifter[], FullTypeId(t: r.typ.firstSon, m: tree.id), ch.info) - if destructor.s != SymId(-1): - c.destroyOps.add DestructorOp(destroyProc: destructor, arg: r.name.symId) - -proc trProcDecl(c: var Context; n: Cursor) = - let r = asRoutine(tree, n) - var c2 = createEntryScope(c.p, c.thisModule, c.lifter, r.body, r.body.info) - c2.isTopLevel = false - copyInto(dest, n): - copyTree dest, r.name - copyTree dest, r.ex - copyTree dest, r.pat - copyTree dest, r.generics - copyTree dest, r.params - copyTree dest, r.pragmas - copyTree dest, r.exc - if r.body.kind == StmtList and r.generics.kind != GenericParams and - not hasBuiltinPragma(c.p, r.pragmas, "nodestroy"): - registerSinkParameters(c2, r.params) - trScope c2, dest, r.body + tr c, body + leaveScope(c, c.currentScope) + +proc registerSinkParameters(c: var Context; params: Cursor) = + var p = params + inc p + while p.kind != ParRi: + let r = takeLocal(p) + if r.typ.typeKind == SinkT: + let destructor = getDestructor(c.lifter[], r.typ.firstSon, p.info) + if destructor != NoSymId: + c.currentScope.destroyOps.add DestructorOp(destroyProc: destructor, arg: r.name.symId) + +proc trProcDecl(c: var Context; n: var Cursor) = + c.dest.add n + var r = takeRoutine(n) + copyTree c.dest, r.name + copyTree c.dest, r.exported + copyTree c.dest, r.pattern + copyTree c.dest, r.typevars + copyTree c.dest, r.params + copyTree c.dest, r.pragmas + copyTree c.dest, r.effects + if r.body.stmtKind == StmtsS and not isGeneric(r): + if hasBuiltinPragma(r.pragmas, NoDestroy): + copyTree c.dest, r.body else: - copyTree dest, r.body - -proc trNestedScope(c: var Context; body: Cursor; kind = Other) = - var bodyScope = createNestedScope(kind, c, body.info) - trScope bodyScope, dest, body + var s2 = createEntryScope(r.body.info) + s2.isTopLevel = false + swap c.currentScope, s2 + registerSinkParameters(c, r.params) + trScope c, r.body + swap c.currentScope, s2 + else: + copyTree c.dest, r.body + c.dest.addParRi() + +proc trNestedScope(c: var Context; body: var Cursor; kind = Other) = + var bodyScope = createNestedScope(kind, c.currentScope, body.info) + swap c.currentScope, bodyScope + trScope c, body + swap c.currentScope, bodyScope + +proc wantParRi(dest: var TokenBuf; n: var Cursor) = + if n.kind == ParRi: + dest.add n + inc n + else: + error "expected ')', but got: ", n -proc trWhile(c: var Context; n: Cursor) = +proc trWhile(c: var Context; n: var Cursor) = #[ while prop(createsObj()) was turned into `while (let tmp = createsObj(); prop(tmp))` by `duplifier.nim` already and `to_stmts` did turn it into: @@ -209,82 +228,87 @@ proc trWhile(c: var Context; n: Cursor) = For these reasons we don't have to do anything special with `cond`. The same reasoning applies to `if` and `case` statements. ]# - let (cond, body) = sons2(tree, n) - copyInto(dest, n): - tr c, dest, cond - trNestedScope c, dest, body, WhileOrBlock - -proc trBlock(c: var Context; n: Cursor) = - let (label, body) = sons2(tree, n) - let labelId = if label.kind == SymDef: label.symId else: AnonBlock - var bodyScope = createNestedScope(WhileOrBlock, c, body.info, labelId) - copyInto(dest, n): - copyTree dest, label - trScope bodyScope, dest, body - -proc trIf(c: var Context; n: Cursor) = - for ch in sons(dest, n): - case ch.kind - of ElifBranch: - let (cond, action) = sons2(tree, ch) - copyInto(dest, ch): - tr c, dest, cond - trNestedScope c, dest, action - of ElseBranch: - copyInto(dest, ch): - trNestedScope c, dest, ch.firstSon - else: - copyTree dest, ch - -proc trCase(c: var Context; n: Cursor) = - copyInto(dest, n): - tr c, dest, n.firstSon - for ch in sonsFrom1(tree, n): - case ch.kind - of OfBranch: - copyInto(dest, ch): - let (first, action) = sons2(tree, ch) - copyTree dest, first - trNestedScope c, dest, action - of ElseBranch: - copyInto(dest, ch): - trNestedScope c, dest, ch.firstSon + copyInto(c.dest, n): + tr c, n + trNestedScope c, n, WhileOrBlock + +proc trBlock(c: var Context; n: var Cursor) = + let label = n.firstSon + let labelId = if label.kind == SymbolDef: label.symId else: c.anonBlock + var bodyScope = createNestedScope(WhileOrBlock, c.currentScope, n.info, labelId) + copyInto(c.dest, n): + takeTree c.dest, n + swap c.currentScope, bodyScope + trScope c, n + swap c.currentScope, bodyScope + +proc trIf(c: var Context; n: var Cursor) = + copyInto(c.dest, n): + while n.kind != ParRi: + case n.substructureKind + of ElifS: + copyInto(c.dest, n): + tr c, n + trNestedScope c, n + of ElseS: + copyInto(c.dest, n): + trNestedScope c, n else: - copyTree dest, ch + takeTree c.dest, n + +proc trCase(c: var Context; n: var Cursor) = + copyInto(c.dest, n): + tr c, n + while n.kind != ParRi: + case n.substructureKind + of OfS: + copyInto(c.dest, n): + takeTree c.dest, n + trNestedScope c, n + of ElseS: + copyInto(c.dest, n): + trNestedScope c, n + else: + takeTree c.dest, n proc tr(c: var Context; n: var Cursor) = - case n.kind - of ReturnStmt: - trReturn(c, dest, n) - of BreakStmt: - trBreak(c, dest, n) - of IfStmt: - trIf c, dest, n - of CaseStmt: - trCase c, dest, n - of BlockStmt: - trBlock c, dest, n - #of Asgn, FirstAsgn: - # trAsgn c, dest, n - of VarDecl, LetDecl, ConstDecl, ResultDecl: - trLocal c, dest, n - of WhileStmt: - trWhile c, dest, n - of DeclarativeNodes, Atoms, Pragmas, TemplateDecl, IteratorDecl, - UsingStmt, CommentStmt, BindStmt, MixinStmt, ContinueStmt: - copyTree dest, n - of ProcDecl, FuncDecl, MacroDecl, MethodDecl, ConverterDecl: - trProcDecl c, dest, n + if isAtom(n) or isDeclarative(n): + takeTree c.dest, n else: - for ch in sons(dest, n): - tr(c, dest, ch) + case n.stmtKind + of RetS: + trReturn(c, n) + of BreakS: + trBreak(c, n) + of IfS: + trIf c, n + of CaseS: + trCase c, n + of BlockS: + trBlock c, n + of VarS, LetS, ConstS, ResultS, CursorS: + trLocal c, n + of WhileS: + trWhile c, n + of ProcS, FuncS, MacroS, MethodS, ConverterS: + trProcDecl c, n + else: + if n.kind == ParLe: + c.dest.add n + inc n + while n.kind != ParRi: + tr(c, n) + wantParRi(c.dest, n) + else: + c.dest.add n + inc n proc injectDestructors*(n: Cursor; lifter: ref LiftingCtx): TokenBuf = - var c = Context(currentScope: createEntryScope(p, thisModule, lifter, StartPos, info), + var c = Context(currentScope: createEntryScope(n.info), anonBlock: pool.syms.getOrIncl("`anonblock.0"), dest: createTokenBuf(400)) var n = n tr(c, n) - leaveScope c, p[result] - genMissingHooks lifter[], p[result] + leaveScope c, c.currentScope + genMissingHooks lifter[] result = ensureMove c.dest diff --git a/src/gear3/duplifier.nim b/src/gear3/duplifier.nim index e5e39983..80ba85e4 100644 --- a/src/gear3/duplifier.nim +++ b/src/gear3/duplifier.nim @@ -129,13 +129,6 @@ proc isResultUsage(n: Cursor): bool {.inline.} = let r = asLocal(res.decl) result = r.kind == ResultY -template copyInto*(dest: var TokenBuf; n: var Cursor; body: untyped) = - assert n.kind == ParLe - dest.add n - inc n - body - wantParRi dest, n - proc trReturn(c: var Context; n: var Cursor) = copyInto c.dest, n: if isResultUsage(n): @@ -359,19 +352,6 @@ proc trOnlyEssentials(c: var Context; n: var Cursor) = dec nested if nested == 0: break -proc hasBuiltinPragma(n: Cursor; kind: PragmaKind): bool = - result = false - var n = n - if n.kind == DotToken: - discard - else: - inc n - while n.kind != ParRi: - if pragmaKind(n) == kind: - result = true - break - skip n - proc trProcDecl(c: var Context; n: var Cursor) = c.dest.add n var r = takeRoutine(n) @@ -500,8 +480,6 @@ proc genLastRead(c: var Context; n: var Cursor; typ: Cursor) = c.dest.copyIntoSymUse ow.s, ow.info c.dest.addParRi() # finish the StmtListExpr -proc isAtom(n: Cursor): bool {.inline.} = n.kind >= ParLe - proc trLocation(c: var Context; n: var Cursor; e: Expects) = # `x` does not own its value as it can be read multiple times. let typ = getType(c.typeCache, n) diff --git a/src/nimony/nimony_model.nim b/src/nimony/nimony_model.nim index 80ad7e0a..9bd4e88c 100644 --- a/src/nimony/nimony_model.nim +++ b/src/nimony/nimony_model.nim @@ -341,6 +341,15 @@ template copyIntoKinds*(dest: var TokenBuf; kinds: array[2, StmtKind]; info: Pac dest.addParRi() dest.addParRi() +template copyInto*(dest: var TokenBuf; n: var Cursor; body: untyped) = + assert n.kind == ParLe + dest.add n + inc n + body + wantParRi dest, n + +proc isAtom*(n: Cursor): bool {.inline.} = n.kind >= ParLe + proc copyIntoSymUse*(dest: var TokenBuf; s: SymId; info: PackedLineInfo) {.inline.} = dest.add symToken(s, info) @@ -447,3 +456,16 @@ proc hookName*(op: AttachedOp): string = const NoSymId* = SymId(0) + +proc hasBuiltinPragma*(n: Cursor; kind: PragmaKind): bool = + result = false + var n = n + if n.kind == DotToken: + discard + else: + inc n + while n.kind != ParRi: + if pragmaKind(n) == kind: + result = true + break + skip n