Skip to content

Commit a5e6707

Browse files
authored
ARC: misc bugfixes (#13156)
* fixes #13102 * closes #13149 * ARC: fixes a move optimizer bug (there are more left regarding array and tuple indexing) * proper fix; fixes #12957 * fixes yet another case object '=' code generation problem
1 parent d88b52c commit a5e6707

File tree

6 files changed

+298
-26
lines changed

6 files changed

+298
-26
lines changed

compiler/dfa.nim

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ proc genUse(c: var Con; orig: PNode) =
597597
if n.kind == nkSym and n.sym.kind in InterestingSyms:
598598
c.code.add Instr(n: orig, kind: use, sym: if orig != n: nil else: n.sym)
599599

600-
proc aliases(obj, field: PNode): bool =
600+
proc aliases*(obj, field: PNode): bool =
601601
var n = field
602602
var obj = obj
603603
while obj.kind in {nkHiddenSubConv, nkHiddenStdConv, nkObjDownConv, nkObjUpConv}:
@@ -646,8 +646,14 @@ proc isAnalysableFieldAccess*(orig: PNode; owner: PSym): bool =
646646
while true:
647647
case n.kind
648648
of nkDotExpr, nkCheckedFieldExpr, nkHiddenSubConv, nkHiddenStdConv,
649-
nkObjDownConv, nkObjUpConv, nkHiddenAddr, nkAddr, nkBracketExpr:
649+
nkObjDownConv, nkObjUpConv, nkHiddenAddr, nkAddr:
650650
n = n[0]
651+
of nkBracketExpr:
652+
# in a[i] the 'i' must be known
653+
if n.len > 1 and n[1].kind in {nkCharLit..nkUInt64Lit}:
654+
n = n[0]
655+
else:
656+
return false
651657
of nkHiddenDeref, nkDerefExpr:
652658
# We "own" sinkparam[].loc but not ourVar[].location as it is a nasty
653659
# pointer indirection.

compiler/injectdestructors.nim

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,8 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
673673
if isUnpackedTuple(ri[0]):
674674
# unpacking of tuple: take over elements
675675
result = newTree(nkFastAsgn, dest, p(ri, c, consumed))
676-
elif isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c):
676+
elif isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c) and
677+
not aliases(dest, ri):
677678
# Rule 3: `=sink`(x, z); wasMoved(z)
678679
var snk = genSink(c, dest, ri)
679680
snk.add ri

compiler/liftdestructors.nim

Lines changed: 77 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import modulegraphs, lineinfos, idents, ast, renderer, semdata,
1717
sighashes, lowerings, options, types, msgs, magicsys, tables
1818

19+
from trees import isCaseObj
20+
1921
type
2022
TLiftCtx = object
2123
g: ModuleGraph
@@ -62,28 +64,45 @@ proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
6264
if c.kind in {attachedAsgn, attachedDeepCopy, attachedSink}:
6365
body.add newAsgnStmt(x, y)
6466

65-
proc fillBodyObj(c: var TLiftCtx; n, body, x, y: PNode) =
67+
proc genAddr(g: ModuleGraph; x: PNode): PNode =
68+
if x.kind == nkHiddenDeref:
69+
checkSonsLen(x, 1, g.config)
70+
result = x[0]
71+
else:
72+
result = newNodeIT(nkHiddenAddr, x.info, makeVarType(x.typ.owner, x.typ))
73+
result.add x
74+
75+
proc destructorCall(g: ModuleGraph; op: PSym; x: PNode): PNode =
76+
result = newNodeIT(nkCall, x.info, op.typ[0])
77+
result.add(newSymNode(op))
78+
result.add genAddr(g, x)
79+
80+
proc fillBodyObj(c: var TLiftCtx; n, body, x, y: PNode; enforceDefaultOp: bool) =
6681
case n.kind
6782
of nkSym:
6883
let f = n.sym
6984
let b = if c.kind == attachedTrace: y else: y.dotField(f)
70-
if sfCursor in f.flags and f.typ.skipTypes(abstractInst).kind in {tyRef, tyProc} and
71-
c.g.config.selectedGC in {gcArc, gcOrc, gcHooks}:
85+
if (sfCursor in f.flags and f.typ.skipTypes(abstractInst).kind in {tyRef, tyProc} and
86+
c.g.config.selectedGC in {gcArc, gcOrc, gcHooks}) or
87+
enforceDefaultOp:
7288
defaultOp(c, f.typ, body, x.dotField(f), b)
7389
else:
7490
fillBody(c, f.typ, body, x.dotField(f), b)
7591
of nkNilLit: discard
7692
of nkRecCase:
77-
if c.kind in {attachedSink, attachedAsgn, attachedDeepCopy}:
93+
# XXX This is only correct for 'attachedSink'!
94+
var localEnforceDefaultOp = enforceDefaultOp
95+
if c.kind == attachedSink:
7896
## the value needs to be destroyed before we assign the selector
7997
## or the value is lost
8098
let prevKind = c.kind
8199
c.kind = attachedDestructor
82-
fillBodyObj(c, n, body, x, y)
100+
fillBodyObj(c, n, body, x, y, enforceDefaultOp = false)
83101
c.kind = prevKind
102+
localEnforceDefaultOp = true
84103

85104
# copy the selector:
86-
fillBodyObj(c, n[0], body, x, y)
105+
fillBodyObj(c, n[0], body, x, y, enforceDefaultOp = false)
87106
# we need to generate a case statement:
88107
var caseStmt = newNodeI(nkCaseStmt, c.info)
89108
# XXX generate 'if' that checks same branches
@@ -96,28 +115,69 @@ proc fillBodyObj(c: var TLiftCtx; n, body, x, y: PNode) =
96115
var branch = copyTree(n[i])
97116
branch[^1] = newNodeI(nkStmtList, c.info)
98117

99-
fillBodyObj(c, n[i].lastSon, branch[^1], x, y)
118+
fillBodyObj(c, n[i].lastSon, branch[^1], x, y,
119+
enforceDefaultOp = localEnforceDefaultOp)
100120
if branch[^1].len == 0: inc emptyBranches
101121
caseStmt.add(branch)
102122
if emptyBranches != n.len-1:
103123
body.add(caseStmt)
104124
of nkRecList:
105-
for t in items(n): fillBodyObj(c, t, body, x, y)
125+
for t in items(n): fillBodyObj(c, t, body, x, y, enforceDefaultOp)
106126
else:
107127
illFormedAstLocal(n, c.g.config)
108128

109-
proc fillBodyObjT(c: var TLiftCtx; t: PType, body, x, y: PNode) =
129+
proc fillBodyObjTImpl(c: var TLiftCtx; t: PType, body, x, y: PNode) =
110130
if t.len > 0 and t[0] != nil:
111-
fillBodyObjT(c, skipTypes(t[0], abstractPtrs), body, x, y)
112-
fillBodyObj(c, t.n, body, x, y)
131+
fillBodyObjTImpl(c, skipTypes(t[0], abstractPtrs), body, x, y)
132+
fillBodyObj(c, t.n, body, x, y, enforceDefaultOp = false)
113133

114-
proc genAddr(g: ModuleGraph; x: PNode): PNode =
115-
if x.kind == nkHiddenDeref:
116-
checkSonsLen(x, 1, g.config)
117-
result = x[0]
134+
proc fillBodyObjT(c: var TLiftCtx; t: PType, body, x, y: PNode) =
135+
var hasCase = isCaseObj(t.n)
136+
var obj = t
137+
while obj.len > 0 and obj[0] != nil:
138+
obj = skipTypes(obj[0], abstractPtrs)
139+
hasCase = hasCase or isCaseObj(obj.n)
140+
141+
if hasCase and c.kind in {attachedAsgn, attachedDeepCopy}:
142+
# assignment for case objects is complex, we do:
143+
# =destroy(dest)
144+
# wasMoved(dest)
145+
# for every field:
146+
# `=` dest.field, src.field
147+
# ^ this is what we used to do, but for 'result = result.sons[0]' it
148+
# destroys 'result' too early.
149+
# So this is what we really need to do:
150+
# let blob {.cursor.} = dest # remembers the old dest.kind
151+
# wasMoved(dest)
152+
# dest.kind = src.kind
153+
# for every field (dependent on dest.kind):
154+
# `=` dest.field, src.field
155+
# =destroy(blob)
156+
var temp = newSym(skTemp, getIdent(c.g.cache, lowerings.genPrefix), c.fn, c.info)
157+
temp.typ = x.typ
158+
incl(temp.flags, sfFromGeneric)
159+
var v = newNodeI(nkVarSection, c.info)
160+
let blob = newSymNode(temp)
161+
v.addVar(blob, x)
162+
body.add v
163+
#body.add newAsgnStmt(blob, x)
164+
165+
var wasMovedCall = newNodeI(nkCall, c.info)
166+
wasMovedCall.add(newSymNode(createMagic(c.g, "wasMoved", mWasMoved)))
167+
wasMovedCall.add x # mWasMoved does not take the address
168+
body.add wasMovedCall
169+
170+
fillBodyObjTImpl(c, t, body, x, y)
171+
when false:
172+
# does not work yet due to phase-ordering problems:
173+
assert t.destructor != nil
174+
body.add destructorCall(c.g, t.destructor, blob)
175+
let prevKind = c.kind
176+
c.kind = attachedDestructor
177+
fillBodyObjTImpl(c, t, body, blob, y)
178+
c.kind = prevKind
118179
else:
119-
result = newNodeIT(nkHiddenAddr, x.info, makeVarType(x.typ.owner, x.typ))
120-
result.add x
180+
fillBodyObjTImpl(c, t, body, x, y)
121181

122182
proc newHookCall(g: ModuleGraph; op: PSym; x, y: PNode): PNode =
123183
#if sfError in op.flags:
@@ -136,11 +196,6 @@ proc newOpCall(op: PSym; x: PNode): PNode =
136196
result.add(newSymNode(op))
137197
result.add x
138198

139-
proc destructorCall(g: ModuleGraph; op: PSym; x: PNode): PNode =
140-
result = newNodeIT(nkCall, x.info, op.typ[0])
141-
result.add(newSymNode(op))
142-
result.add genAddr(g, x)
143-
144199
proc newDeepCopyCall(op: PSym; x, y: PNode): PNode =
145200
result = newAsgnStmt(x, newOpCall(op, y))
146201

lib/system/seqs_v2.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ proc newSeqPayload(cap, elemSize: int): pointer {.compilerRtl, raises: [].} =
4848
result = nil
4949
5050
proc prepareSeqAdd(len: int; p: pointer; addlen, elemSize: int): pointer {.
51-
compilerRtl, noSideEffect, raises: [].} =
51+
noSideEffect, raises: [].} =
5252
{.noSideEffect.}:
5353
template `+!`(p: pointer, s: int): pointer =
5454
cast[pointer](cast[int](p) +% s)

tests/arc/tcaseobj.nim

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
discard """
2+
valgrind: true
3+
cmd: "nim c --gc:arc -d:useMalloc $file"
4+
output: '''myobj destroyed
5+
myobj destroyed
6+
myobj destroyed
7+
A
8+
B
9+
begin
10+
end
11+
myobj destroyed
12+
'''
13+
"""
14+
15+
# bug #13102
16+
17+
type
18+
D = ref object
19+
R = object
20+
case o: bool
21+
of false:
22+
discard
23+
of true:
24+
field: D
25+
26+
iterator things(): R =
27+
when true:
28+
var
29+
unit = D()
30+
while true:
31+
yield R(o: true, field: unit)
32+
else:
33+
while true:
34+
var
35+
unit = D()
36+
yield R(o: true, field: unit)
37+
38+
proc main =
39+
var i = 0
40+
for item in things():
41+
discard item.field
42+
inc i
43+
if i == 2: break
44+
45+
main()
46+
47+
# bug #13149
48+
49+
type
50+
TMyObj = object
51+
p: pointer
52+
len: int
53+
54+
proc `=destroy`(o: var TMyObj) =
55+
if o.p != nil:
56+
dealloc o.p
57+
o.p = nil
58+
echo "myobj destroyed"
59+
60+
proc `=`(dst: var TMyObj, src: TMyObj) =
61+
`=destroy`(dst)
62+
dst.p = alloc(src.len)
63+
dst.len = src.len
64+
65+
proc `=sink`(dst: var TMyObj, src: TMyObj) =
66+
`=destroy`(dst)
67+
dst.p = src.p
68+
dst.len = src.len
69+
70+
type
71+
TObjKind = enum Z, A, B
72+
TCaseObj = object
73+
case kind: TObjKind
74+
of Z: discard
75+
of A:
76+
x1: int # this int plays important role
77+
x2: TMyObj
78+
of B:
79+
y: TMyObj
80+
81+
proc testSinks: TCaseObj =
82+
result = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5)))
83+
result = TCaseObj(kind: B, y: TMyObj(len: 3, p: alloc(3)))
84+
85+
proc use(x: TCaseObj) = discard
86+
87+
proc testCopies(i: int) =
88+
var a: array[2, TCaseObj]
89+
a[i] = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5)))
90+
a[i+1] = a[i] # copy, cannot move
91+
use(a[i])
92+
93+
let x1 = testSinks()
94+
testCopies(0)
95+
96+
# bug #12957
97+
98+
type
99+
PegKind* = enum
100+
pkCharChoice,
101+
pkSequence
102+
Peg* = object ## type that represents a PEG
103+
case kind: PegKind
104+
of pkCharChoice: charChoice: ref set[char]
105+
else: discard
106+
sons: seq[Peg]
107+
108+
proc charSet*(s: set[char]): Peg =
109+
## constructs a PEG from a character set `s`
110+
result = Peg(kind: pkCharChoice)
111+
new(result.charChoice)
112+
result.charChoice[] = s
113+
114+
proc len(a: Peg): int {.inline.} = return a.sons.len
115+
proc myadd(d: var Peg, s: Peg) {.inline.} = add(d.sons, s)
116+
117+
proc sequence*(a: openArray[Peg]): Peg =
118+
result = Peg(kind: pkSequence, sons: @[])
119+
when false:
120+
#works too:
121+
result.myadd(a[0])
122+
result.myadd(a[1])
123+
for x in items(a):
124+
# works:
125+
#result.sons.add(x)
126+
# fails:
127+
result.myadd x
128+
if result.len == 1:
129+
result = result.sons[0] # this must not move!
130+
131+
when true:
132+
# bug #12957
133+
134+
proc p =
135+
echo "A"
136+
let x = sequence([charSet({'a'..'z', 'A'..'Z', '_'}),
137+
charSet({'a'..'z', 'A'..'Z', '0'..'9', '_'})])
138+
echo "B"
139+
p()
140+
141+
proc testSubObjAssignment =
142+
echo "begin"
143+
# There must be extactly one element in the array constructor!
144+
let x = sequence([charSet({'a'..'z', 'A'..'Z', '_'})])
145+
echo "end"
146+
testSubObjAssignment()

0 commit comments

Comments
 (0)