Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion compiler/aliasanalysis.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,27 @@ proc skipConvDfa*(n: PNode): PNode =
result = result[1]
else: break

proc hasUniqueEnvAccess*(orig: PNode): bool =
## Returns true if the access goes through a closure environment marked with nfUniqueEnv.
## Such accesses are known to be unique and can be treated specially by the DFA.
result = false
var n = orig
while true:
case n.kind
of PathKinds0 - {nkHiddenDeref, nkDerefExpr}:
n = n[0]
of PathKinds1:
n = n[1]
of nkHiddenDeref, nkDerefExpr:
if nfUniqueEnv in n.flags:
return true
n = n[0]
else:
return

proc isAnalysableFieldAccess*(orig: PNode; owner: PSym): bool =
var n = orig
var sawUniqueEnv = false
while true:
case n.kind
of PathKinds0 - {nkHiddenDeref, nkDerefExpr}:
Expand All @@ -33,15 +52,23 @@ proc isAnalysableFieldAccess*(orig: PNode; owner: PSym): bool =
# pointer indirection.
# bug #14159, we cannot reason about sinkParam[].location as it can
# still be shared for tyRef.
# Closure environment accesses marked with nfUniqueEnv are known to be
# unique, so allow the DFA to recognize last-uses through them:
if nfUniqueEnv in n.flags:
sawUniqueEnv = true
n = n[0]
continue
n = n[0]
return n.kind == nkSym and n.sym.owner == owner and
(n.sym.typ.skipTypes(abstractInst-{tyOwned}).kind in {tyOwned})
else: break
# XXX Allow closure deref operations here if we know
# the owner controlled the closure allocation?
# When we've traversed through nfUniqueEnv marked derefs (closure environments),
# we can relax the parameter check since the environment is uniquely owned:
result = n.kind == nkSym and n.sym.owner == owner and
{sfGlobal, sfThread, sfCursor} * n.sym.flags == {} and
(n.sym.kind != skParam or isSinkParam(n.sym)) # or n.sym.typ.kind == tyVar)
(sawUniqueEnv or n.sym.kind != skParam or isSinkParam(n.sym)) # or n.sym.typ.kind == tyVar)
# Note: There is a different move analyzer possible that checks for
# consume(param.key); param.key = newValue for all paths. Then code like
#
Expand Down
6 changes: 5 additions & 1 deletion compiler/astdef.nim
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,9 @@ type
# because openSym experimental switch is disabled
# gives warning instead
nfLazyType # node has a lazy type
nfUniqueEnv # the hidden deref is unique for closure environments;
# tells the DFA engine to assume uniqueness so that
# last-use detection can work for env.field accesses

TNodeFlags* = set[TNodeFlag]
TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 47)
Expand Down Expand Up @@ -866,7 +869,8 @@ const
nfFromTemplate, nfDefaultRefsParam,
nfExecuteOnReload, nfLastRead,
nfFirstWrite, nfSkipFieldChecking,
nfDisabledOpenSym, nfLazyType}
nfDisabledOpenSym, nfLazyType,
nfUniqueEnv}
namePos* = 0
patternPos* = 1 # empty except for term rewriting macros
genericParamsPos* = 2
Expand Down
7 changes: 4 additions & 3 deletions compiler/closureiters.nim
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ const

proc newStateAccess(ctx: var Ctx): PNode =
result = rawIndirectAccess(newSymNode(getEnvParam(ctx.fn)),
getStateField(ctx.g, ctx.fn), ctx.fn.info)
getStateField(ctx.g, ctx.fn), ctx.fn.info, uniqueEnv = true)

proc newStateAssgn(ctx: var Ctx, toValue: PNode): PNode =
# Creates state assignment:
Expand All @@ -206,7 +206,8 @@ proc newEnvVar(ctx: var Ctx, name: string, typ: PType): PSym =
result = addUniqueField(envParam.typ.elementType, result, ctx.g.cache, ctx.idgen)

proc newEnvVarAccess(ctx: Ctx, s: PSym): PNode =
result = rawIndirectAccess(newSymNode(getEnvParam(ctx.fn)), s, ctx.fn.info)
result = rawIndirectAccess(newSymNode(getEnvParam(ctx.fn)), s, ctx.fn.info,
uniqueEnv = true)

proc newTempVarAccess(ctx: Ctx, s: PSym): PNode =
result = newSymNode(s, ctx.fn.info)
Expand Down Expand Up @@ -1436,7 +1437,7 @@ proc liftLocals(c: var Ctx, n: PNode): PNode =
let e = getEnvParam(c.fn)
let field = getFieldFromObj(e.typ.elementType, s)
assert(field != nil)
result = rawIndirectAccess(newSymNode(e), field, n.info)
result = rawIndirectAccess(newSymNode(e), field, n.info, uniqueEnv = true)
# elif c.varStates.getOrDefault(s.itemId, localNotSeen) != localNotSeen:
# echo "Not lifting ", s.name.s

Expand Down
2 changes: 2 additions & 0 deletions compiler/ic/enum2nif.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1473,6 +1473,7 @@ proc genFlags*(s: set[TNodeFlag]; dest: var string) =
of nfSkipFieldChecking: dest.add "s0"
of nfDisabledOpenSym: dest.add "d3"
of nfLazyType: dest.add "l1"
of nfUniqueEnv: dest.add "u"


proc parse*(t: typedesc[TNodeFlag]; s: string): set[TNodeFlag] =
Expand Down Expand Up @@ -1533,6 +1534,7 @@ proc parse*(t: typedesc[TNodeFlag]; s: string): set[TNodeFlag] =
inc i
else: result.incl nfSem
of 't': result.incl nfTransf
of 'u': result.incl nfUniqueEnv
of 'w': result.incl nfFirstWrite
else: discard
inc i
Expand Down
6 changes: 4 additions & 2 deletions compiler/injectdestructors.nim
Original file line number Diff line number Diff line change
Expand Up @@ -815,9 +815,11 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
nkCallKinds + nkLiterals:
result = p(n, c, s, consumed)
elif ((n.kind == nkSym and isSinkParam(n.sym)) or isAnalysableFieldAccess(n, c.owner)) and
isLastRead(n, c, s) and not (n.kind == nkSym and isCursor(n)):
(hasUniqueEnvAccess(n) or isLastRead(n, c, s)) and not (n.kind == nkSym and isCursor(n)):
# Sinked params can be consumed only once. We need to reset the memory
# to disable the destructor which we have not elided
# to disable the destructor which we have not elided.
# For unique env accesses (closure environments), we can skip the isLastRead check
# because each field in the environment is only accessed by one closure.
result = destructiveMoveVar(n, c, s)
elif n.kind in {nkHiddenSubConv, nkHiddenStdConv, nkConv}:
result = copyTree(n)
Expand Down
16 changes: 8 additions & 8 deletions compiler/lambdalifting.nim
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ proc liftIterSym*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: PSym): PN
if owner.isIterator:
let it = getHiddenParam(g, owner)
addUniqueField(it.typ.skipTypes({tyOwned})[0], hp, g.cache, idgen)
env = indirectAccess(newSymNode(it), hp, hp.info)
env = indirectAccess(newSymNode(it), hp, hp.info, uniqueEnv = true)
else:
let e = newSym(skLet, iter.name, idgen, owner, n.info)
e.typ = hp.typ
Expand Down Expand Up @@ -537,10 +537,10 @@ proc accessViaEnvParam(g: ModuleGraph; n: PNode; owner: PSym): PNode =
assert obj.kind == tyObject
let field = getFieldFromObj(obj, s)
if field != nil:
return rawIndirectAccess(access, field, n.info)
return rawIndirectAccess(access, field, n.info, uniqueEnv = true)
let upField = lookupInRecord(obj.n, getIdent(g.cache, upName))
if upField == nil: break
access = rawIndirectAccess(access, upField, n.info)
access = rawIndirectAccess(access, upField, n.info, uniqueEnv = true)
obj = access.typ.baseClass
localError(g.config, n.info, "internal error: environment misses: " & s.name.s)
result = n
Expand Down Expand Up @@ -583,7 +583,7 @@ proc getUpViaParam(g: ModuleGraph; owner: PSym): PNode =
if upField == nil:
localError(g.config, owner.info, "could not find up reference for closure iter")
else:
result = rawIndirectAccess(result, upField, p.info)
result = rawIndirectAccess(result, upField, p.info, uniqueEnv = true)

proc rawClosureCreation(owner: PSym;
d: var DetectionPass; c: var LiftingPass;
Expand Down Expand Up @@ -618,7 +618,7 @@ proc rawClosureCreation(owner: PSym;
for i in 1..<owner.typ.n.len:
let local = owner.typ.n[i].sym
if local.id in d.capturedVars:
let fieldAccess = indirectAccess(env, local, env.info)
let fieldAccess = indirectAccess(env, local, env.info, uniqueEnv = true)
# add ``env.param = param``
result.add(newAsgnStmt(fieldAccess, newSymNode(local), env.info))
if owner.kind != skMacro:
Expand Down Expand Up @@ -658,7 +658,7 @@ proc getUpForIter(g: ModuleGraph; owner, iterOwner: PSym, expectedUpTyp: PType):
if upField == nil:
return nil
p = upField
res = rawIndirectAccess(res, upField, p.info)
res = rawIndirectAccess(res, upField, p.info, uniqueEnv = true)
res

proc closureCreationForIter(owner: PSym, iter: PNode;
Expand All @@ -672,7 +672,7 @@ proc closureCreationForIter(owner: PSym, iter: PNode;
if iterOwner.isIterator:
let it = getHiddenParam(d.graph, iterOwner)
addUniqueField(it.typ.skipTypes({tyOwned, tyRef, tyPtr}), v, d.graph.cache, d.idgen)
vnode = indirectAccess(newSymNode(it), v, v.info)
vnode = indirectAccess(newSymNode(it), v, v.info, uniqueEnv = true)
else:
vnode = v.newSymNode
var vs = newNodeI(nkVarSection, iter.info)
Expand Down Expand Up @@ -701,7 +701,7 @@ proc accessViaEnvVar(n: PNode; owner: PSym; d: var DetectionPass;
let obj = access.typ.skipTypes({tyOwned, tyRef, tyPtr})
let field = getFieldFromObj(obj, n.sym)
if field != nil:
result = rawIndirectAccess(access, field, n.info)
result = rawIndirectAccess(access, field, n.info, uniqueEnv = true)
else:
localError(d.graph.config, n.info, "internal error: not part of closure object type")
result = n
Expand Down
24 changes: 17 additions & 7 deletions compiler/lowerings.nim
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,14 @@ proc rawAddField*(obj: PType; field: PSym) =
propagateToOwner(obj, field.typ)
fieldCheck()

proc rawIndirectAccess*(a: PNode; field: PSym; info: TLineInfo): PNode =
proc rawIndirectAccess*(a: PNode; field: PSym; info: TLineInfo;
uniqueEnv = false): PNode =
# returns a[].field as a node
assert field.kind == skField
var deref = newNodeI(nkHiddenDeref, info)
deref.typ = a.typ.skipTypes(abstractInst)[0]
if uniqueEnv:
deref.flags.incl nfUniqueEnv
deref.add a
result = newNodeI(nkDotExpr, info)
result.add deref
Expand Down Expand Up @@ -252,10 +255,13 @@ proc newDotExpr*(obj, b: PSym): PNode =
result.add newSymNode(field)
result.typ = field.typ

proc indirectAccess*(a: PNode, b: ItemId, info: TLineInfo): PNode =
proc indirectAccess*(a: PNode, b: ItemId, info: TLineInfo;
uniqueEnv = false): PNode =
# returns a[].b as a node
var deref = newNodeI(nkHiddenDeref, info)
deref.typ = a.typ.skipTypes(abstractInst).elementType
if uniqueEnv:
deref.flags.incl nfUniqueEnv
var t = deref.typ.skipTypes(abstractInst)
var field: PSym
while true:
Expand All @@ -275,10 +281,13 @@ proc indirectAccess*(a: PNode, b: ItemId, info: TLineInfo): PNode =
result.add newSymNode(field)
result.typ = field.typ

proc indirectAccess*(a: PNode, b: string, info: TLineInfo; cache: IdentCache): PNode =
proc indirectAccess*(a: PNode, b: string, info: TLineInfo; cache: IdentCache;
uniqueEnv = false): PNode =
# returns a[].b as a node
var deref = newNodeI(nkHiddenDeref, info)
deref.typ = a.typ.skipTypes(abstractInst).elementType
if uniqueEnv:
deref.flags.incl nfUniqueEnv
var t = deref.typ.skipTypes(abstractInst)
var field: PSym
let bb = getIdent(cache, b)
Expand Down Expand Up @@ -310,12 +319,13 @@ proc getFieldFromObj*(t: PType; v: PSym): PSym =
if t == nil: break
t = t.skipTypes(skipPtrs)

proc indirectAccess*(a: PNode, b: PSym, info: TLineInfo): PNode =
proc indirectAccess*(a: PNode, b: PSym, info: TLineInfo;
uniqueEnv = false): PNode =
# returns a[].b as a node
result = indirectAccess(a, b.itemId, info)
result = indirectAccess(a, b.itemId, info, uniqueEnv)

proc indirectAccess*(a, b: PSym, info: TLineInfo): PNode =
result = indirectAccess(newSymNode(a), b, info)
proc indirectAccess*(a, b: PSym, info: TLineInfo; uniqueEnv = false): PNode =
result = indirectAccess(newSymNode(a), b, info, uniqueEnv)

proc genAddrOf*(n: PNode; idgen: IdGenerator; typeKind = tyPtr): PNode =
result = newNodeI(nkAddr, n.info, 1)
Expand Down
Loading