parent
8a6c5edc43
commit
4ec5cbac0d
5 changed files with 196 additions and 45 deletions
|
|
@ -13,6 +13,17 @@
|
||||||
## algorithm.
|
## algorithm.
|
||||||
## The used data structure is "union find" with path compression.
|
## The used data structure is "union find" with path compression.
|
||||||
|
|
||||||
|
## We perform two passes over the AST:
|
||||||
|
## - Pass one (``computeLiveRanges``): collect livetimes of local
|
||||||
|
## variables and whether they are potentially re-assigned.
|
||||||
|
## - Pass two (``traverse``): combine local variables to abstract "graphs".
|
||||||
|
## Strict func checking: Ensure that graphs that are connected to
|
||||||
|
## const parameters are not mutated.
|
||||||
|
## Cursor inference: Ensure that potential cursors are not
|
||||||
|
## borrowed from locations that are connected to a graph
|
||||||
|
## that is mutated during the liveness of the cursor.
|
||||||
|
## (We track all possible mutations of a graph.)
|
||||||
|
|
||||||
import ast, types, lineinfos, options, msgs, renderer
|
import ast, types, lineinfos, options, msgs, renderer
|
||||||
from trees import getMagic, whichPragma
|
from trees import getMagic, whichPragma
|
||||||
from wordrecg import wNoSideEffect
|
from wordrecg import wNoSideEffect
|
||||||
|
|
@ -26,7 +37,8 @@ type
|
||||||
|
|
||||||
VarFlag = enum
|
VarFlag = enum
|
||||||
ownsData,
|
ownsData,
|
||||||
preventCursor
|
preventCursor,
|
||||||
|
isReassigned
|
||||||
|
|
||||||
VarIndexKind = enum
|
VarIndexKind = enum
|
||||||
isEmptyRoot,
|
isEmptyRoot,
|
||||||
|
|
@ -266,36 +278,38 @@ proc allRoots(n: PNode; result: var seq[PSym]; followDotExpr = true) =
|
||||||
else:
|
else:
|
||||||
discard "nothing to do"
|
discard "nothing to do"
|
||||||
|
|
||||||
proc analyseAsgn(c: var Partitions; dest: var VarIndex; n: PNode) =
|
proc destMightOwn(c: var Partitions; dest: var VarIndex; n: PNode) =
|
||||||
|
## Analyse if 'n' is an expression that owns the data, if so mark 'dest'
|
||||||
|
## with 'ownsData'.
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkEmpty, nkCharLit..nkNilLit:
|
of nkEmpty, nkCharLit..nkNilLit:
|
||||||
# primitive literals including the empty are harmless:
|
# primitive literals including the empty are harmless:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkCast, nkConv:
|
of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkCast, nkConv:
|
||||||
analyseAsgn(c, dest, n[1])
|
destMightOwn(c, dest, n[1])
|
||||||
|
|
||||||
of nkIfStmt, nkIfExpr:
|
of nkIfStmt, nkIfExpr:
|
||||||
for i in 0..<n.len:
|
for i in 0..<n.len:
|
||||||
analyseAsgn(c, dest, n[i].lastSon)
|
destMightOwn(c, dest, n[i].lastSon)
|
||||||
|
|
||||||
of nkCaseStmt:
|
of nkCaseStmt:
|
||||||
for i in 1..<n.len:
|
for i in 1..<n.len:
|
||||||
analyseAsgn(c, dest, n[i].lastSon)
|
destMightOwn(c, dest, n[i].lastSon)
|
||||||
|
|
||||||
of nkStmtList, nkStmtListExpr:
|
of nkStmtList, nkStmtListExpr:
|
||||||
if n.len > 0:
|
if n.len > 0:
|
||||||
analyseAsgn(c, dest, n[^1])
|
destMightOwn(c, dest, n[^1])
|
||||||
|
|
||||||
of nkClosure:
|
of nkClosure:
|
||||||
for i in 1..<n.len:
|
for i in 1..<n.len:
|
||||||
analyseAsgn(c, dest, n[i])
|
destMightOwn(c, dest, n[i])
|
||||||
# you must destroy a closure:
|
# you must destroy a closure:
|
||||||
dest.flags.incl ownsData
|
dest.flags.incl ownsData
|
||||||
|
|
||||||
of nkObjConstr:
|
of nkObjConstr:
|
||||||
for i in 1..<n.len:
|
for i in 1..<n.len:
|
||||||
analyseAsgn(c, dest, n[i])
|
destMightOwn(c, dest, n[i])
|
||||||
if hasDestructor(n.typ):
|
if hasDestructor(n.typ):
|
||||||
# you must destroy a ref object:
|
# you must destroy a ref object:
|
||||||
dest.flags.incl ownsData
|
dest.flags.incl ownsData
|
||||||
|
|
@ -303,7 +317,7 @@ proc analyseAsgn(c: var Partitions; dest: var VarIndex; n: PNode) =
|
||||||
of nkCurly, nkBracket, nkPar, nkTupleConstr:
|
of nkCurly, nkBracket, nkPar, nkTupleConstr:
|
||||||
inc c.inConstructor
|
inc c.inConstructor
|
||||||
for son in n:
|
for son in n:
|
||||||
analyseAsgn(c, dest, son)
|
destMightOwn(c, dest, son)
|
||||||
dec c.inConstructor
|
dec c.inConstructor
|
||||||
if n.typ.skipTypes(abstractInst).kind == tySequence:
|
if n.typ.skipTypes(abstractInst).kind == tySequence:
|
||||||
# you must destroy a sequence:
|
# you must destroy a sequence:
|
||||||
|
|
@ -322,7 +336,7 @@ proc analyseAsgn(c: var Partitions; dest: var VarIndex; n: PNode) =
|
||||||
|
|
||||||
of nkDotExpr, nkBracketExpr, nkHiddenDeref, nkDerefExpr,
|
of nkDotExpr, nkBracketExpr, nkHiddenDeref, nkDerefExpr,
|
||||||
nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr, nkAddr, nkHiddenAddr:
|
nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr, nkAddr, nkHiddenAddr:
|
||||||
analyseAsgn(c, dest, n[0])
|
destMightOwn(c, dest, n[0])
|
||||||
|
|
||||||
of nkCallKinds:
|
of nkCallKinds:
|
||||||
if hasDestructor(n.typ):
|
if hasDestructor(n.typ):
|
||||||
|
|
@ -348,7 +362,7 @@ proc analyseAsgn(c: var Partitions; dest: var VarIndex; n: PNode) =
|
||||||
# list of dependencies via the 'hasDestructor' check for
|
# list of dependencies via the 'hasDestructor' check for
|
||||||
# the root's symbol.
|
# the root's symbol.
|
||||||
if hasDestructor(n[i].typ.skipTypes({tyVar, tySink, tyLent, tyGenericInst, tyAlias})):
|
if hasDestructor(n[i].typ.skipTypes({tyVar, tySink, tyLent, tyGenericInst, tyAlias})):
|
||||||
analyseAsgn(c, dest, n[i])
|
destMightOwn(c, dest, n[i])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# something we cannot handle:
|
# something we cannot handle:
|
||||||
|
|
@ -389,24 +403,36 @@ proc deps(c: var Partitions; dest, src: PNode) =
|
||||||
if dest.kind == nkSym:
|
if dest.kind == nkSym:
|
||||||
let vid = variableId(c, dest.sym)
|
let vid = variableId(c, dest.sym)
|
||||||
if vid >= 0:
|
if vid >= 0:
|
||||||
analyseAsgn(c, c.s[vid], src)
|
destMightOwn(c, c.s[vid], src)
|
||||||
# do not borrow from a different local variable, this is easier
|
|
||||||
# than tracking reassignments, consider 'var cursor = local; local = newNode()'
|
|
||||||
if src.kind == nkSym:
|
if src.kind == nkSym:
|
||||||
if (src.sym.kind in {skVar, skResult, skTemp} or
|
let s = src.sym
|
||||||
(src.sym.kind in {skLet, skParam, skForVar} and hasDisabledAsgn(src.sym.typ))):
|
if {sfGlobal, sfThread} * s.flags != {} or hasDisabledAsgn(s.typ):
|
||||||
|
# do not borrow from a global variable or from something with a
|
||||||
|
# disabled assignment operator.
|
||||||
c.s[vid].flags.incl preventCursor
|
c.s[vid].flags.incl preventCursor
|
||||||
elif src.sym.kind in {skVar, skResult, skTemp, skLet, skForVar}:
|
when false: echo "A not a cursor: ", dest.sym, " ", s
|
||||||
# XXX: we need to compute variable alive ranges before doing anything else:
|
else:
|
||||||
let srcid = variableId(c, src.sym)
|
let srcid = variableId(c, s)
|
||||||
if srcid >= 0 and preventCursor in c.s[srcid].flags:
|
if srcid >= 0:
|
||||||
# you cannot borrow from a local that lives shorter than 'vid':
|
if s.kind notin {skResult, skParam} and (
|
||||||
if c.s[srcid].aliveStart > c.s[vid].aliveStart or
|
c.s[srcid].aliveEnd < c.s[vid].aliveEnd):
|
||||||
c.s[srcid].aliveEnd < c.s[vid].aliveEnd:
|
# you cannot borrow from a local that lives shorter than 'vid':
|
||||||
|
when false: echo "B not a cursor ", dest.sym, " ", c.s[srcid].aliveEnd, " ", c.s[vid].aliveEnd
|
||||||
|
c.s[vid].flags.incl preventCursor
|
||||||
|
elif {isReassigned, preventCursor} * c.s[srcid].flags != {}:
|
||||||
|
# you cannot borrow from something that is re-assigned:
|
||||||
|
when false: echo "C not a cursor ", dest.sym, " ", c.s[srcid].flags
|
||||||
c.s[vid].flags.incl preventCursor
|
c.s[vid].flags.incl preventCursor
|
||||||
|
|
||||||
if src.kind == nkSym and hasDestructor(src.typ):
|
#if src.kind == nkSym and hasDestructor(src.typ):
|
||||||
rhsIsSink(c, src)
|
# rhsIsSink(c, src)
|
||||||
|
|
||||||
|
const
|
||||||
|
nodesToIgnoreSet = {nkNone..pred(nkSym), succ(nkSym)..nkNilLit,
|
||||||
|
nkTypeSection, nkProcDef, nkConverterDef,
|
||||||
|
nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo,
|
||||||
|
nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt,
|
||||||
|
nkExportStmt, nkPragma, nkCommentStmt, nkBreakState, nkTypeOfExpr}
|
||||||
|
|
||||||
proc traverse(c: var Partitions; n: PNode) =
|
proc traverse(c: var Partitions; n: PNode) =
|
||||||
inc c.abstractTime
|
inc c.abstractTime
|
||||||
|
|
@ -418,11 +444,11 @@ proc traverse(c: var Partitions; n: PNode) =
|
||||||
if child.kind == nkVarTuple and last.kind in {nkPar, nkTupleConstr}:
|
if child.kind == nkVarTuple and last.kind in {nkPar, nkTupleConstr}:
|
||||||
if child.len-2 != last.len: return
|
if child.len-2 != last.len: return
|
||||||
for i in 0..<child.len-2:
|
for i in 0..<child.len-2:
|
||||||
registerVariable(c, child[i])
|
#registerVariable(c, child[i])
|
||||||
deps(c, child[i], last[i])
|
deps(c, child[i], last[i])
|
||||||
else:
|
else:
|
||||||
for i in 0..<child.len-2:
|
for i in 0..<child.len-2:
|
||||||
registerVariable(c, child[i])
|
#registerVariable(c, child[i])
|
||||||
deps(c, child[i], last)
|
deps(c, child[i], last)
|
||||||
of nkAsgn, nkFastAsgn:
|
of nkAsgn, nkFastAsgn:
|
||||||
traverse(c, n[0])
|
traverse(c, n[0])
|
||||||
|
|
@ -432,15 +458,8 @@ proc traverse(c: var Partitions; n: PNode) =
|
||||||
deps(c, n[0], n[1])
|
deps(c, n[0], n[1])
|
||||||
of nkSym:
|
of nkSym:
|
||||||
dec c.abstractTime
|
dec c.abstractTime
|
||||||
if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar, skParam}:
|
|
||||||
let id = variableId(c, n.sym)
|
|
||||||
if id >= 0:
|
|
||||||
c.s[id].aliveEnd = max(c.s[id].aliveEnd, c.abstractTime)
|
|
||||||
|
|
||||||
of nkNone..pred(nkSym), succ(nkSym)..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef,
|
of nodesToIgnoreSet:
|
||||||
nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo,
|
|
||||||
nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt,
|
|
||||||
nkExportStmt, nkPragma, nkCommentStmt, nkBreakState, nkTypeOfExpr:
|
|
||||||
dec c.abstractTime
|
dec c.abstractTime
|
||||||
discard "do not follow the construct"
|
discard "do not follow the construct"
|
||||||
of nkCallKinds:
|
of nkCallKinds:
|
||||||
|
|
@ -514,6 +533,79 @@ proc traverse(c: var Partitions; n: PNode) =
|
||||||
else:
|
else:
|
||||||
for child in n: traverse(c, child)
|
for child in n: traverse(c, child)
|
||||||
|
|
||||||
|
proc computeLiveRanges(c: var Partitions; n: PNode) =
|
||||||
|
# first pass: Compute live ranges for locals.
|
||||||
|
# **Watch out!** We must traverse the tree like 'traverse' does
|
||||||
|
# so that the 'c.abstractTime' is consistent.
|
||||||
|
inc c.abstractTime
|
||||||
|
case n.kind
|
||||||
|
of nkLetSection, nkVarSection:
|
||||||
|
for child in n:
|
||||||
|
let last = lastSon(child)
|
||||||
|
computeLiveRanges(c, last)
|
||||||
|
if child.kind == nkVarTuple and last.kind in {nkPar, nkTupleConstr}:
|
||||||
|
if child.len-2 != last.len: return
|
||||||
|
for i in 0..<child.len-2:
|
||||||
|
registerVariable(c, child[i])
|
||||||
|
#deps(c, child[i], last[i])
|
||||||
|
else:
|
||||||
|
for i in 0..<child.len-2:
|
||||||
|
registerVariable(c, child[i])
|
||||||
|
#deps(c, child[i], last)
|
||||||
|
|
||||||
|
of nkAsgn, nkFastAsgn:
|
||||||
|
computeLiveRanges(c, n[0])
|
||||||
|
computeLiveRanges(c, n[1])
|
||||||
|
if n[0].kind == nkSym:
|
||||||
|
let vid = variableId(c, n[0].sym)
|
||||||
|
if vid >= 0:
|
||||||
|
c.s[vid].flags.incl isReassigned
|
||||||
|
|
||||||
|
of nkSym:
|
||||||
|
dec c.abstractTime
|
||||||
|
if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar, skParam}:
|
||||||
|
let id = variableId(c, n.sym)
|
||||||
|
if id >= 0:
|
||||||
|
c.s[id].aliveEnd = max(c.s[id].aliveEnd, c.abstractTime)
|
||||||
|
|
||||||
|
of nodesToIgnoreSet:
|
||||||
|
dec c.abstractTime
|
||||||
|
discard "do not follow the construct"
|
||||||
|
of nkCallKinds:
|
||||||
|
for child in n: computeLiveRanges(c, child)
|
||||||
|
|
||||||
|
let parameters = n[0].typ
|
||||||
|
let L = if parameters != nil: parameters.len else: 0
|
||||||
|
|
||||||
|
for i in 1..<n.len:
|
||||||
|
let it = n[i]
|
||||||
|
if it.kind == nkSym and i < L:
|
||||||
|
let paramType = parameters[i].skipTypes({tyGenericInst, tyAlias})
|
||||||
|
if not paramType.isCompileTimeOnly and paramType.kind == tyVar:
|
||||||
|
let vid = variableId(c, it.sym)
|
||||||
|
if vid >= 0:
|
||||||
|
c.s[vid].flags.incl isReassigned
|
||||||
|
|
||||||
|
of nkAddr, nkHiddenAddr:
|
||||||
|
computeLiveRanges(c, n[0])
|
||||||
|
if n[0].kind == nkSym:
|
||||||
|
let vid = variableId(c, n[0].sym)
|
||||||
|
if vid >= 0:
|
||||||
|
c.s[vid].flags.incl preventCursor
|
||||||
|
|
||||||
|
of nkPragmaBlock:
|
||||||
|
computeLiveRanges(c, n.lastSon)
|
||||||
|
of nkWhileStmt, nkForStmt, nkParForStmt:
|
||||||
|
for child in n: computeLiveRanges(c, child)
|
||||||
|
# analyse loops twice so that 'abstractTime' suffices to detect cases
|
||||||
|
# like:
|
||||||
|
# while cond:
|
||||||
|
# mutate(graph)
|
||||||
|
# connect(graph, cursorVar)
|
||||||
|
for child in n: computeLiveRanges(c, child)
|
||||||
|
else:
|
||||||
|
for child in n: computeLiveRanges(c, child)
|
||||||
|
|
||||||
proc computeGraphPartitions*(s: PSym; n: PNode; cursorInference = false): Partitions =
|
proc computeGraphPartitions*(s: PSym; n: PNode; cursorInference = false): Partitions =
|
||||||
result = Partitions(performCursorInference: cursorInference)
|
result = Partitions(performCursorInference: cursorInference)
|
||||||
if s.kind notin {skModule, skMacro}:
|
if s.kind notin {skModule, skMacro}:
|
||||||
|
|
@ -523,6 +615,9 @@ proc computeGraphPartitions*(s: PSym; n: PNode; cursorInference = false): Partit
|
||||||
if resultPos < s.ast.safeLen:
|
if resultPos < s.ast.safeLen:
|
||||||
registerVariable(result, s.ast[resultPos])
|
registerVariable(result, s.ast[resultPos])
|
||||||
|
|
||||||
|
computeLiveRanges(result, n)
|
||||||
|
# resart the timer for the second pass:
|
||||||
|
result.abstractTime = 0
|
||||||
traverse(result, n)
|
traverse(result, n)
|
||||||
|
|
||||||
proc dangerousMutation(g: MutationInfo; v: VarIndex): bool =
|
proc dangerousMutation(g: MutationInfo; v: VarIndex): bool =
|
||||||
|
|
@ -560,7 +655,7 @@ proc computeCursors*(s: PSym; n: PNode; config: ConfigRef) =
|
||||||
var par = computeGraphPartitions(s, n, true)
|
var par = computeGraphPartitions(s, n, true)
|
||||||
for i in 0 ..< par.s.len:
|
for i in 0 ..< par.s.len:
|
||||||
let v = addr(par.s[i])
|
let v = addr(par.s[i])
|
||||||
if v.flags == {} and v.sym.kind notin {skParam, skResult} and
|
if v.flags * {ownsData, preventCursor} == {} and v.sym.kind notin {skParam, skResult} and
|
||||||
v.sym.flags * {sfThread, sfGlobal} == {} and hasDestructor(v.sym.typ) and
|
v.sym.flags * {sfThread, sfGlobal} == {} and hasDestructor(v.sym.typ) and
|
||||||
v.sym.typ.skipTypes({tyGenericInst, tyAlias}).kind != tyOwned:
|
v.sym.typ.skipTypes({tyGenericInst, tyAlias}).kind != tyOwned:
|
||||||
let rid = root(par, i)
|
let rid = root(par, i)
|
||||||
|
|
|
||||||
53
tests/arc/top_no_cursor2.nim
Normal file
53
tests/arc/top_no_cursor2.nim
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
discard """
|
||||||
|
output: '''true
|
||||||
|
true
|
||||||
|
true
|
||||||
|
true
|
||||||
|
true'''
|
||||||
|
cmd: "nim c --gc:arc $file"
|
||||||
|
"""
|
||||||
|
# bug #15361
|
||||||
|
|
||||||
|
type
|
||||||
|
ErrorNodeKind = enum Branch, Leaf
|
||||||
|
Error = ref object
|
||||||
|
case kind: ErrorNodeKind
|
||||||
|
of Branch:
|
||||||
|
left: Error
|
||||||
|
right: Error
|
||||||
|
of Leaf:
|
||||||
|
leafError: string
|
||||||
|
input: string
|
||||||
|
|
||||||
|
proc ret(input: string, lefterr, righterr: Error): Error =
|
||||||
|
result = Error(kind: Branch, left: lefterr, right: righterr, input: input)
|
||||||
|
|
||||||
|
proc parser() =
|
||||||
|
var rerrors: Error
|
||||||
|
let lerrors = Error(
|
||||||
|
kind: Leaf,
|
||||||
|
leafError: "first error",
|
||||||
|
input: "123 ;"
|
||||||
|
)
|
||||||
|
# If you remove "block" - everything works
|
||||||
|
block:
|
||||||
|
let rresult = Error(
|
||||||
|
kind: Leaf,
|
||||||
|
leafError: "second error",
|
||||||
|
input: ";"
|
||||||
|
)
|
||||||
|
# this assignment is needed too
|
||||||
|
rerrors = rresult
|
||||||
|
|
||||||
|
# Returns Error(kind: Branch, left: lerrors, right: rerrors, input: "some val")
|
||||||
|
# needs to be a proc call for some reason, can't inline the result
|
||||||
|
var data = ret(input = "some val", lefterr = lerrors, righterr = rerrors)
|
||||||
|
|
||||||
|
echo data.left.leafError == "first error"
|
||||||
|
echo data.left.input == "123 ;"
|
||||||
|
# stacktrace shows this line
|
||||||
|
echo data.right.leafError == "second error"
|
||||||
|
echo data.right.input == ";"
|
||||||
|
echo data.input == "some val"
|
||||||
|
|
||||||
|
parser()
|
||||||
|
|
@ -52,7 +52,7 @@ _ = (
|
||||||
blitTmp, ";")
|
blitTmp, ";")
|
||||||
lvalue = _[0]
|
lvalue = _[0]
|
||||||
lnext_cursor = _[1]
|
lnext_cursor = _[1]
|
||||||
`=sink`(result.value, lvalue)
|
`=sink`(result.value, move lvalue)
|
||||||
-- end of expandArc ------------------------
|
-- end of expandArc ------------------------
|
||||||
--expandArc: tt
|
--expandArc: tt
|
||||||
|
|
||||||
|
|
@ -148,7 +148,7 @@ proc p1(): Maybe =
|
||||||
var lnext: string
|
var lnext: string
|
||||||
(lvalue, lnext) = (lresult, ";")
|
(lvalue, lnext) = (lresult, ";")
|
||||||
|
|
||||||
result.value = lvalue
|
result.value = move lvalue
|
||||||
|
|
||||||
proc tissue15130 =
|
proc tissue15130 =
|
||||||
doAssert p1().value == @[123]
|
doAssert p1().value == @[123]
|
||||||
|
|
|
||||||
|
|
@ -23,17 +23,18 @@ joinable: false
|
||||||
type T = object
|
type T = object
|
||||||
|
|
||||||
proc `=`(lhs: var T, rhs: T) =
|
proc `=`(lhs: var T, rhs: T) =
|
||||||
echo "assign"
|
echo "assign"
|
||||||
|
|
||||||
proc `=destroy`(v: var T) =
|
proc `=destroy`(v: var T) =
|
||||||
echo "destroy"
|
echo "destroy"
|
||||||
|
|
||||||
proc use(x: T) = discard
|
proc use(x: T) = discard
|
||||||
|
|
||||||
proc usedToBeBlock =
|
proc usedToBeBlock =
|
||||||
var v1 : T
|
var v1 = T()
|
||||||
var v2 : T = v1
|
var v2: T = v1
|
||||||
use v1
|
discard addr(v2) # prevent cursorfication
|
||||||
|
use v1
|
||||||
|
|
||||||
usedToBeBlock()
|
usedToBeBlock()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,14 +96,16 @@ proc info =
|
||||||
allocCount = 0
|
allocCount = 0
|
||||||
deallocCount = 0
|
deallocCount = 0
|
||||||
|
|
||||||
|
proc copy(a: Matrix): Matrix = a
|
||||||
|
|
||||||
proc test1 =
|
proc test1 =
|
||||||
var a = matrix(5, 5, 1.0)
|
var a = matrix(5, 5, 1.0)
|
||||||
var b = a
|
var b = copy a
|
||||||
var c = a + b
|
var c = a + b
|
||||||
|
|
||||||
proc test2 =
|
proc test2 =
|
||||||
var a = matrix(5, 5, 1.0)
|
var a = matrix(5, 5, 1.0)
|
||||||
var b = a
|
var b = copy a
|
||||||
var c = -a
|
var c = -a
|
||||||
|
|
||||||
proc test3 =
|
proc test3 =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue