diff --git a/src/gear3/destroyer.nim b/src/gear3/destroyer.nim new file mode 100644 index 00000000..660ca951 --- /dev/null +++ b/src/gear3/destroyer.nim @@ -0,0 +1,314 @@ +# +# +# 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)`. + +]## + +import std / assertions +include nifprelude +import nifindexes, symparser, treemangler +import ".." / nimony / [nimony_model, programs, typenav, decls] +import lifter + +const + NoLabel = SymId(0) + +type + ScopeKind = enum + Other + WhileOrBlock + DestructorOp = object + destroyProc: SymId + arg: SymId + + Scope = object + label: SymId + kind: ScopeKind + destroyOps: seq[DestructorOp] + info: PackedLineInfo + parent: ptr Scope + 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(label: label, + kind: kind, destroyOps: @[], info: info, parent: addr(parent), + isTopLevel: false) + +proc createEntryScope(info: PackedLineInfo): Scope = + Scope(label: NoLabel, + kind: Other, destroyOps: @[], info: info, parent: nil, + isTopLevel: true) + +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 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 Context; label: SymId) = + #[ Consider: + + var x = f() + block: + break # do we want to destroy x here? No. + + ]# + var it = addr(c.currentScope) + while it != nil and it.label != label: + leaveScope(c, it[]) + it = it.parent + if it != nil and it.label == label: + leaveScope(c, it[]) + else: + raiseAssert "do not know which block to leave" + +proc leaveAnonBlock(c: var Context) = + var it = addr(c.currentScope) + while it != nil and it.kind != WhileOrBlock: + leaveScope(c, it[]) + it = it.parent + if it != nil and it.kind == WhileOrBlock: + leaveScope(c, it[]) + else: + raiseAssert "do not know which block to leave" + +proc trBreak(c: var Context; n: var Cursor) = + let lab = n.firstSon + if lab.kind == Symbol: + leaveNamedBlock(c, lab.symId) + else: + leaveAnonBlock(c) + takeTree c.dest, n + +proc trReturn(c: var Context; n: var Cursor) = + var it = addr(c.currentScope) + while it != nil: + leaveScope(c, it[]) + it = it.parent + takeTree c.dest, n + +when not defined(nimony): + proc tr(c: var Context; n: var Cursor) + +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, 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: + 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: 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: + + 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. + ]# + 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: + 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) = + if isAtom(n) or isDeclarative(n): + takeTree c.dest, n + else: + 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(n.info), + anonBlock: pool.syms.getOrIncl("`anonblock.0"), + dest: createTokenBuf(400)) + var n = n + tr(c, n) + leaveScope c, c.currentScope + genMissingHooks lifter[] + result = ensureMove c.dest diff --git a/src/gear3/duplifier.nim b/src/gear3/duplifier.nim new file mode 100644 index 00000000..80ba85e4 --- /dev/null +++ b/src/gear3/duplifier.nim @@ -0,0 +1,618 @@ +# +# +# 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). + +]## + +import std / [assertions] +include nifprelude +import nifindexes, symparser, treemangler, lifter +import ".." / nimony / [nimony_model, programs, decls, typenav] + +type + Context = object + dest: TokenBuf + lifter: ref LiftingCtx + reportLastUse: bool + typeCache: TypeCache + tmpCounter: int + + Expects = enum + DontCare, + WillBeOwned, + WantNonOwner, + WantOwner + +# -------------- helpers ---------------------------------------- + +proc isLastRead(c: Context; n: Cursor): bool = + # 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, ArrAtX}: + 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: + 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 + +# ----------------------------------------------------------- + +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) + wantParRi c.dest, n + +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 trReturn(c: var Context; n: var Cursor) = + copyInto c.dest, n: + if isResultUsage(n): + copyTree c.dest, n + else: + tr c, n, WantOwner + +proc evalLeftHandSide(c: var Context; le: var Cursor): TokenBuf = + result = createTokenBuf(10) + if le.kind == Symbol: + # simple enough: + takeTree result, le + else: + let typ = getType(c.typeCache, le) + let info = le.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; 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 c.dest, CallS, info: + copyIntoSymUse c.dest, destroyProc, info + copyIntoSymUse c.dest, arg, info + +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 + 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 != NoSymId and arg.kind != StringLit: + copyIntoKind c.dest, CallS, info: + copyIntoSymUse c.dest, hookProc, info + tr c, arg, WillBeOwned + else: + tr c, arg, WillBeOwned + +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) + 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: var 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 + + But we should really prefer to call `=copy(x, y)` here for some of these cases. + ]# + 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 == NoSymId: + # the type has no destructor, there is nothing interesting to do: + trSons c, n, DontCare + + else: + let isNotFirstAsgn = true # XXX Adapt this once we have "isFirstAsgn" analysis + var leCopy = le + var lhs = evalLeftHandSide(c, leCopy) + if constructsValue(ri): + # `x = f()` is turned into `=destroy(x); x =bitcopy f()`. + if isNotFirstAsgn: + callDestroy(c, destructor, lhs) + copyInto c.dest, n: + copyTree c.dest, lhs + n = ri + tr c, n, WillBeOwned + elif isLastRead(c, ri): + if isNotFirstAsgn and potentialSelfAsgn(le, ri): + # `let tmp = y; =wasMoved(y); =destroy(x); x =bitcopy tmp` + 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, destructor, lhs) + copyInto c.dest, n: + copyTree c.dest, lhs + var n = ri + 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) + 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, destructor, lhs) + copyInto c.dest, n: + 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: 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 + +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 != 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 + inc n + tr c, n, e2 + skipParRi n + +proc trOnlyEssentials(c: var Context; n: var Cursor) = + var nested = 0 + while true: + case n.kind + 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: + var fn = pool.syms[n.symId] + extractBasename fn + case fn + 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: 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: + 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[], typ) + +type + OwningTemp = object + active: bool + s: SymId + info: PackedLineInfo + +template owningTempDefault(): OwningTemp = + OwningTemp(active: false, s: NoSymId, info: NoLineInfo) + +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(active: true, s: s, info: info) + +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.addParRi() # finish the StmtListExpr + +proc trCall(c: var Context; n: var Cursor; e: Expects) = + var ow = owningTempDefault() + let retType = getType(c.typeCache, n) + if hasDestructor(c, retType) and e == WantNonOwner: + 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 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 c.dest, ow + +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 + inc n + while n.kind != ParRi: + 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 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, 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, typ, info, CursorS) + takeTree c.dest, n + + c.dest.addParRi() # finish the VarDecl + + let hookProc = getHook(c.lifter[], attachedWasMoved, typ, info) + if hookProc != NoSymId: + copyIntoKind c.dest, CallS, info: + copyIntoSymUse c.dest, hookProc, info + copyIntoKind c.dest, HaddrX, info: + copyTree c.dest, ex + + 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: var Cursor; e: Expects) = + # `x` does not own its value as it can be read multiple times. + let typ = getType(c.typeCache, n) + if e == WantOwner and hasDestructor(c, typ): + if isLastRead(c, n): + genLastRead(c, n, typ) + else: + let info = n.info + # translate `x` to `=dup(x)`: + let hookProc = getHook(c.lifter[], attachedDup, typ, info) + if hookProc != NoSymId: + copyIntoKind c.dest, CallS, info: + copyIntoSymUse c.dest, hookProc, info + if isAtom(n): + takeTree c.dest, n + else: + trSons c, n, DontCare + elif isAtom(n): + takeTree c.dest, n + else: + trSons c, n, DontCare + elif isAtom(n): + takeTree c.dest, n + else: + 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, 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 c.dest, n: + genLastRead(c, n, typ) + else: + 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 c.dest, n: + tr c, n, e + else: + 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: + 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(), + dest: createTokenBuf(400)) + var n = n + tr(c, n, WantNonOwner) + genMissingHooks lifter[] + + result = ensureMove(c.dest) diff --git a/src/gear3/gear3.nim b/src/gear3/gear3.nim index f61f48a2..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" diff --git a/src/nimony/derefs.nim b/src/nimony/derefs.nim index 18205205..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 @@ -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 @@ -471,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 292d75f2..9bd4e88c 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) @@ -340,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) @@ -364,6 +374,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: @@ -394,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 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 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: