# # # The Nim Compiler # (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## Semantic checking for 'parallel'. # - codegen needs to support mSlice (+) # - lowerings must not perform unnecessary copies (+) # - slices should become "nocopy" to openArray (+) # - need to perform bound checks (+) # # - parallel needs to insert a barrier (+) # - passed arguments need to be ensured to be "const" # - what about 'f(a)'? --> f shouldn't have side effects anyway # - passed arrays need to be ensured not to alias # - passed slices need to be ensured to be disjoint (+) # - output slices need special logic (+) import ast, astalgo, idents, lowerings, magicsys, guards, sempass2, msgs, renderer, types from trees import getMagic from strutils import `%` discard """ one major problem: spawn f(a[i]) inc i spawn f(a[i]) is valid, but spawn f(a[i]) spawn f(a[i]) inc i is not! However, spawn f(a[i]) if guard: inc i spawn f(a[i]) is not valid either! --> We need a flow dependent analysis here. However: while foo: spawn f(a[i]) inc i spawn f(a[i]) Is not valid either! --> We should really restrict 'inc' to loop endings? The heuristic that we implement here (that has no false positives) is: Usage of 'i' in a slice *after* we determined the stride is invalid! """ type TDirection = enum ascending, descending MonotonicVar = object v, alias: PSym # to support the ordinary 'countup' iterator # we need to detect aliases lower, upper, stride: PNode dir: TDirection blacklisted: bool # blacklisted variables that are not monotonic AnalysisCtx = object locals: seq[MonotonicVar] slices: seq[tuple[x,a,b: PNode, spawnId: int, inLoop: bool]] guards: TModel # nested guards args: seq[PSym] # args must be deeply immutable spawns: int # we can check that at last 1 spawn is used in # the 'parallel' section currentSpawnId: int inLoop: int proc initAnalysisCtx(): AnalysisCtx = result.locals = @[] result.slices = @[] result.args = @[] result.guards = @[] proc lookupSlot(c: AnalysisCtx; s: PSym): int = for i in 0.. = 0: return addr(c.locals[s]) let L = c.locals.len c.locals.setLen(L+1) c.locals[L].v = v return addr(c.locals[L]) proc gatherArgs(c: var AnalysisCtx; n: PNode) = for i in 0.. = 0 and c.locals[s].stride != nil: localError(n.info, "invalid usage of counter after increment") else: for i in 0 .. " & ?b & " (bounds check)") proc checkBounds(c: AnalysisCtx; arr, idx: PNode) = checkLe(c, arr.lowBound, idx) checkLe(c, idx, arr.highBound) proc addLowerBoundAsFacts(c: var AnalysisCtx) = for v in c.locals: if not v.blacklisted: c.guards.addFactLe(v.lower, newSymNode(v.v)) proc addSlice(c: var AnalysisCtx; n: PNode; x, le, ri: PNode) = checkLocal(c, n) let le = le.canon let ri = ri.canon # perform static bounds checking here; and not later! let oldState = c.guards.len addLowerBoundAsFacts(c) c.checkBounds(x, le) c.checkBounds(x, ri) c.guards.setLen(oldState) c.slices.add((x, le, ri, c.currentSpawnId, c.inLoop > 0)) proc overlap(m: TModel; x,y,c,d: PNode) = # X..Y and C..D overlap iff (X <= D and C <= Y) case proveLe(m, c, y) of impUnknown: case proveLe(m, x, d) of impNo: discard of impUnknown, impYes: localError(x.info, "cannot prove: $# > $#; required for ($#)..($#) disjoint from ($#)..($#)" % [?c, ?y, ?x, ?y, ?c, ?d]) of impYes: case proveLe(m, x, d) of impUnknown: localError(x.info, "cannot prove: $# > $#; required for ($#)..($#) disjoint from ($#)..($#)" % [?x, ?d, ?x, ?y, ?c, ?d]) of impYes: localError(x.info, "($#)..($#) not disjoint from ($#)..($#)" % [?c, ?y, ?x, ?y, ?c, ?d]) of impNo: discard of impNo: discard proc stride(c: AnalysisCtx; n: PNode): BiggestInt = if isLocal(n): let s = c.lookupSlot(n.sym) if s >= 0 and c.locals[s].stride != nil: result = c.locals[s].stride.intVal else: for i in 0 .. = 0 and c.locals[s].stride != nil: result = n +@ c.locals[s].stride.intVal else: result = n elif n.safeLen > 0: result = shallowCopy(n) for i in 0 .. = 0 and m >= 0): # ah I cannot resist the temptation and add another sweet heuristic: # if both slices have the form (i+k)..(i+k) and (i+m)..(i+m) we # check they are disjoint and k < stride and m < stride: overlap(c.guards, x.a, x.b, y.a, y.b) let stride = min(c.stride(x.a), c.stride(y.a)) if k < stride and m < stride: discard else: localError(x.x.info, "cannot prove ($#)..($#) disjoint from ($#)..($#)" % [?x.a, ?x.b, ?y.a, ?y.b]) else: localError(x.x.info, "cannot prove ($#)..($#) disjoint from ($#)..($#)" % [?x.a, ?x.b, ?y.a, ?y.b]) proc analyse(c: var AnalysisCtx; n: PNode) proc analyseSons(c: var AnalysisCtx; n: PNode) = for i in 0 .. 0: result = shallowCopy(n) for i in 0 .. < n.len: result.sons[i] = transformSlices(n.sons[i]) else: result = n proc transformSpawn(owner: PSym; n, barrier: PNode): PNode proc transformSpawnSons(owner: PSym; n, barrier: PNode): PNode = result = shallowCopy(n) for i in 0 .. < n.len: result.sons[i] = transformSpawn(owner, n.sons[i], barrier) proc transformSpawn(owner: PSym; n, barrier: PNode): PNode = case n.kind of nkVarSection, nkLetSection: result = nil for it in n: let b = it.lastSon if getMagic(b) == mSpawn: if it.len != 3: localError(it.info, "invalid context for 'spawn'") let m = transformSlices(b) if result.isNil: result = newNodeI(nkStmtList, n.info) result.add n let t = b[1][0].typ.sons[0] if spawnResult(t, true) == srByVar: result.add wrapProcForSpawn(owner, m, b.typ, barrier, it[0]) it.sons[it.len-1] = emptyNode else: it.sons[it.len-1] = wrapProcForSpawn(owner, m, b.typ, barrier, nil) if result.isNil: result = n of nkAsgn, nkFastAsgn: let b = n[1] if getMagic(b) == mSpawn and (let t = b[1][0].typ.sons[0]; spawnResult(t, true) == srByVar): let m = transformSlices(b) return wrapProcForSpawn(owner, m, b.typ, barrier, n[0]) result = transformSpawnSons(owner, n, barrier) of nkCallKinds: if getMagic(n) == mSpawn: result = transformSlices(n) return wrapProcForSpawn(owner, result, n.typ, barrier, nil) result = transformSpawnSons(owner, n, barrier) elif n.safeLen > 0: result = transformSpawnSons(owner, n, barrier) else: result = n proc checkArgs(a: var AnalysisCtx; n: PNode) = discard "too implement" proc generateAliasChecks(a: AnalysisCtx; result: PNode) = discard "too implement" proc liftParallel*(owner: PSym; n: PNode): PNode = # this needs to be called after the 'for' loop elimination # first pass: # - detect monotonic local integer variables # - detect used slices # - detect used arguments #echo "PAR ", renderTree(n) var a = initAnalysisCtx() let body = n.lastSon analyse(a, body) if a.spawns == 0: localError(n.info, "'parallel' section without 'spawn'") checkSlicesAreDisjoint(a) checkArgs(a, body) var varSection = newNodeI(nkVarSection, n.info) var temp = newSym(skTemp, getIdent"barrier", owner, n.info) temp.typ = magicsys.getCompilerProc("Barrier").typ incl(temp.flags, sfFromGeneric) let tempNode = newSymNode(temp) varSection.addVar tempNode let barrier = genAddrOf(tempNode) result = newNodeI(nkStmtList, n.info) generateAliasChecks(a, result) result.add varSection result.add callCodegenProc("openBarrier", barrier) result.add transformSpawn(owner, body, barrier) result.add callCodegenProc("closeBarrier", barrier)