'parallel' statement almost working

This commit is contained in:
Araq 2014-05-22 08:41:50 +02:00
commit 417b9f5a1d
10 changed files with 470 additions and 115 deletions

View file

@ -1636,7 +1636,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
of mSlurp..mQuoteAst: of mSlurp..mQuoteAst:
localError(e.info, errXMustBeCompileTime, e.sons[0].sym.name.s) localError(e.info, errXMustBeCompileTime, e.sons[0].sym.name.s)
of mSpawn: of mSpawn:
let n = lowerings.wrapProcForSpawn(p.module.module, e.sons[1]) let n = lowerings.wrapProcForSpawn(p.module.module, e[1], e.typ, nil, nil)
expr(p, n, d) expr(p, n, d)
of mParallel: of mParallel:
let n = semparallel.liftParallel(p.module.module, e) let n = semparallel.liftParallel(p.module.module, e)

View file

@ -672,12 +672,8 @@ proc simpleSlice*(a, b: PNode): BiggestInt =
else: else:
result = -1 result = -1
proc proveLe*(m: TModel; a, b: PNode): TImplication = proc ple(m: TModel; a, b: PNode): TImplication =
let res = canon(opLe.buildCall(a, b)) template `<=?`(a,b): expr = ple(m,a,b) == impYes
#echo renderTree(res)
# we hardcode lots of axioms here:
let a = res[1]
let b = res[2]
# 0 <= 3 # 0 <= 3
if a.isValue and b.isValue: if a.isValue and b.isValue:
return if leValue(a, b): impYes else: impNo return if leValue(a, b): impYes else: impNo
@ -692,26 +688,46 @@ proc proveLe*(m: TModel; a, b: PNode): TImplication =
# x <= x # x <= x
if sameTree(a, b): return impYes if sameTree(a, b): return impYes
# x <= x+c iff 0 <= c # 0 <= x.len
if b.getMagic in someAdd and sameTree(a, b[1]): if b.getMagic in someLen and a.isValue:
return proveLe(m, zero(), b[2]) if a.intVal <= 0: return impYes
# x+c <= x iff c <= 0 # x <= y+c if 0 <= c and x <= y
if a.getMagic in someAdd and sameTree(b, a[1]): if b.getMagic in someAdd and zero() <=? b[2] and a <=? b[1]: return impYes
return proveLe(m, a[2], zero())
# x <= x*c if 1 <= c and 0 <= x: # x+c <= y if c <= 0 and x <= y
if b.getMagic in someMul and sameTree(a, b[1]): if a.getMagic in someAdd and a[2] <=? zero() and a[1] <=? b: return impYes
if proveLe(m, one(), b[2]) == impYes and proveLe(m, zero(), a) == impYes:
return impYes
# x div c <= x if 1 <= c and 0 <= x: # x <= y*c if 1 <= c and x <= y and 0 <= y
if a.getMagic in someDiv and sameTree(a[1], b): if b.getMagic in someMul:
if proveLe(m, one(), a[2]) == impYes and proveLe(m, zero(), b) == impYes: if a <=? b[1] and one() <=? b[2] and zero() <=? b[1]: return impYes
return impYes
# x div c <= y if 1 <= c and 0 <= y and x <= y:
if a.getMagic in someDiv:
if one() <=? a[2] and zero() <=? b and a[1] <=? b: return impYes
# slightly subtle:
# x <= max(y, z) iff x <= y or x <= z
# note that 'x <= max(x, z)' is a special case of the above rule
if b.getMagic in someMax:
if a <=? b[1] or a <=? b[2]: return impYes
# min(x, y) <= z iff x <= z or y <= z
if a.getMagic in someMin:
if a[1] <=? b or a[2] <=? b: return impYes
# use the knowledge base: # use the knowledge base:
return doesImply(m, res) return doesImply(m, opLe.buildCall(a, b))
proc proveLe*(m: TModel; a, b: PNode): TImplication =
#echo "ROOT ", renderTree(a), " <=? ", b.rendertree
let x = canon(opLe.buildCall(a, b))
#echo renderTree(res)
result = ple(m, x[1], x[2])
if result == impUnknown:
# try an alternative: a <= b iff not (b < a) iff not (b+1 <= a):
let y = canon(opLe.buildCall(opAdd.buildCall(b, one()), a))
result = ~ple(m, y[1], y[2])
proc addFactLe*(m: var TModel; a, b: PNode) = proc addFactLe*(m: var TModel; a, b: PNode) =
m.add canon(opLe.buildCall(a, b)) m.add canon(opLe.buildCall(a, b))

View file

@ -13,6 +13,8 @@ const
genPrefix* = ":tmp" # prefix for generated names genPrefix* = ":tmp" # prefix for generated names
import ast, astalgo, types, idents, magicsys, msgs, options import ast, astalgo, types, idents, magicsys, msgs, options
from guards import createMagic
from trees import getMagic
proc newTupleAccess*(tup: PNode, i: int): PNode = proc newTupleAccess*(tup: PNode, i: int): PNode =
result = newNodeIT(nkBracketExpr, tup.info, tup.typ.skipTypes( result = newNodeIT(nkBracketExpr, tup.info, tup.typ.skipTypes(
@ -80,19 +82,23 @@ proc newDotExpr(obj, b: PSym): PNode =
addSon(result, newSymNode(field)) addSon(result, newSymNode(field))
result.typ = field.typ result.typ = field.typ
proc indirectAccess*(a: PNode, b: PSym, info: TLineInfo): PNode = proc indirectAccess*(a: PNode, b: string, info: TLineInfo): PNode =
# returns a[].b as a node # returns a[].b as a node
var deref = newNodeI(nkHiddenDeref, info) var deref = newNodeI(nkHiddenDeref, info)
deref.typ = a.typ.sons[0] deref.typ = a.typ.skipTypes(abstractInst).sons[0]
assert deref.typ.kind == tyObject assert deref.typ.kind == tyObject
let field = getSymFromList(deref.typ.n, getIdent(b.name.s & $b.id)) let field = getSymFromList(deref.typ.n, getIdent(b))
assert field != nil, b.name.s assert field != nil, b
addSon(deref, a) addSon(deref, a)
result = newNodeI(nkDotExpr, info) result = newNodeI(nkDotExpr, info)
addSon(result, deref) addSon(result, deref)
addSon(result, newSymNode(field)) addSon(result, newSymNode(field))
result.typ = field.typ result.typ = field.typ
proc indirectAccess*(a: PNode, b: PSym, info: TLineInfo): PNode =
# returns a[].b as a node
result = indirectAccess(a, b.name.s & $b.id, info)
proc indirectAccess*(a, b: PSym, info: TLineInfo): PNode = proc indirectAccess*(a, b: PSym, info: TLineInfo): PNode =
result = indirectAccess(newSymNode(a), b, info) result = indirectAccess(newSymNode(a), b, info)
@ -102,6 +108,11 @@ proc genAddrOf*(n: PNode): PNode =
result.typ = newType(tyPtr, n.typ.owner) result.typ = newType(tyPtr, n.typ.owner)
result.typ.rawAddSon(n.typ) result.typ.rawAddSon(n.typ)
proc genDeref*(n: PNode): PNode =
result = newNodeIT(nkHiddenDeref, n.info,
n.typ.skipTypes(abstractInst).sons[0])
result.add n
proc callCodegenProc*(name: string, arg1: PNode; proc callCodegenProc*(name: string, arg1: PNode;
arg2, arg3: PNode = nil): PNode = arg2, arg3: PNode = nil): PNode =
result = newNodeI(nkCall, arg1.info) result = newNodeI(nkCall, arg1.info)
@ -114,14 +125,83 @@ proc callCodegenProc*(name: string, arg1: PNode;
if arg2 != nil: result.add arg2 if arg2 != nil: result.add arg2
if arg3 != nil: result.add arg3 if arg3 != nil: result.add arg3
# we have 4 cases to consider:
# - a void proc --> nothing to do
# - a proc returning GC'ed memory --> requires a future
# - a proc returning non GC'ed memory --> pass as hidden 'var' parameter
# - not in a parallel environment --> requires a future for memory safety
type
TSpawnResult = enum
srVoid, srFuture, srByVar
TFutureKind = enum
futInvalid # invalid type T for 'Future[T]'
futGC # Future of a GC'ed type
futBlob # Future of a blob type
proc spawnResult(t: PType; inParallel: bool): TSpawnResult =
if t.isEmptyType: srVoid
elif inParallel and not containsGarbageCollectedRef(t): srByVar
else: srFuture
proc futureKind(t: PType): TFutureKind =
if t.skipTypes(abstractInst).kind in {tyRef, tyString, tySequence}: futGC
elif containsGarbageCollectedRef(t): futInvalid
else: futBlob
discard """
We generate roughly this:
proc f_wrapper(args) =
var a = args.a # copy strings/seqs; thread transfer; not generated for
# the 'parallel' statement
var b = args.b
args.fut = createFuture(thread, sizeof(T)) # optional
nimArgsPassingDone() # signal parent that the work is done
args.fut.blob = f(a, b, ...)
# - or -
f(a, b, ...)
stmtList:
var scratchObj
scratchObj.a = a
scratchObj.b = b
nimSpawn(f_wrapper, addr scratchObj)
scratchObj.fut # optional
"""
proc createNimCreateFutureCall(fut, threadParam: PNode): PNode =
let size = newNodeIT(nkCall, fut.info, getSysType(tyInt))
size.add newSymNode(createMagic("sizeof", mSizeOf))
assert fut.typ.kind == tyGenericInst
size.add newNodeIT(nkType, fut.info, fut.typ.sons[1])
let castExpr = newNodeIT(nkCast, fut.info, fut.typ)
castExpr.add emptyNode
castExpr.add callCodeGenProc("nimCreateFuture", threadParam, size)
result = newFastAsgnStmt(fut, castExpr)
proc createWrapperProc(f: PNode; threadParam, argsParam: PSym; proc createWrapperProc(f: PNode; threadParam, argsParam: PSym;
varSection, call, barrier: PNode): PSym = varSection, call, barrier, fut: PNode): PSym =
var body = newNodeI(nkStmtList, f.info) var body = newNodeI(nkStmtList, f.info)
body.add varSection body.add varSection
if barrier != nil: if barrier != nil:
body.add callCodeGenProc("barrierEnter", barrier) body.add callCodeGenProc("barrierEnter", barrier)
body.add callCodeGenProc("nimArgsPassingDone", newSymNode(threadParam)) if fut != nil:
body.add call body.add createNimCreateFutureCall(fut, threadParam.newSymNode)
if barrier == nil:
body.add callCodeGenProc("nimFutureCreateCondVar", fut)
body.add callCodeGenProc("nimArgsPassingDone", threadParam.newSymNode)
if fut != nil:
body.add newAsgnStmt(indirectAccess(fut,
if fut.typ.futureKind==futGC: "data" else: "blob", fut.info), call)
if barrier == nil:
body.add callCodeGenProc("nimFutureSignal", fut)
else:
body.add call
if barrier != nil: if barrier != nil:
body.add callCodeGenProc("barrierLeave", barrier) body.add callCodeGenProc("barrierLeave", barrier)
@ -151,10 +231,148 @@ proc createCastExpr(argsParam: PSym; objType: PType): PNode =
result.typ = newType(tyPtr, objType.owner) result.typ = newType(tyPtr, objType.owner)
result.typ.rawAddSon(objType) result.typ.rawAddSon(objType)
proc wrapProcForSpawn*(owner: PSym; n: PNode; barrier: PNode = nil): PNode = proc setupArgsForConcurrency(n: PNode; objType: PType; scratchObj: PSym,
result = newNodeI(nkStmtList, n.info) castExpr, call, varSection, result: PNode) =
if n.kind notin nkCallKinds or not n.typ.isEmptyType: let formals = n[0].typ.n
localError(n.info, "'spawn' takes a call expression of type void") let tmpName = getIdent(genPrefix)
for i in 1 .. <n.len:
# we pick n's type here, which hopefully is 'tyArray' and not
# 'tyOpenArray':
var argType = n[i].typ.skipTypes(abstractInst)
if i < formals.len and formals[i].typ.kind == tyVar:
localError(n[i].info, "'spawn'ed function cannot have a 'var' parameter")
elif containsTyRef(argType):
localError(n[i].info, "'spawn'ed function cannot refer to 'ref'/closure")
let fieldname = if i < formals.len: formals[i].sym.name else: tmpName
var field = newSym(skField, fieldname, objType.owner, n.info)
field.typ = argType
objType.addField(field)
result.add newFastAsgnStmt(newDotExpr(scratchObj, field), n[i])
var temp = newSym(skTemp, tmpName, objType.owner, n.info)
temp.typ = argType
incl(temp.flags, sfFromGeneric)
var vpart = newNodeI(nkIdentDefs, n.info, 3)
vpart.sons[0] = newSymNode(temp)
vpart.sons[1] = ast.emptyNode
vpart.sons[2] = indirectAccess(castExpr, field, n.info)
varSection.add vpart
call.add(newSymNode(temp))
proc getRoot*(n: PNode): PSym =
## ``getRoot`` takes a *path* ``n``. A path is an lvalue expression
## like ``obj.x[i].y``. The *root* of a path is the symbol that can be
## determined as the owner; ``obj`` in the example.
case n.kind
of nkSym:
if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar}:
result = n.sym
of nkDotExpr, nkBracketExpr, nkHiddenDeref, nkDerefExpr,
nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr:
result = getRoot(n.sons[0])
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
result = getRoot(n.sons[1])
of nkCallKinds:
if getMagic(n) == mSlice: result = getRoot(n.sons[1])
else: discard
proc newIntLit(value: BiggestInt): PNode =
result = nkIntLit.newIntNode(value)
result.typ = getSysType(tyInt)
proc genHigh(n: PNode): PNode =
if skipTypes(n.typ, abstractVar).kind in {tyArrayConstr, tyArray}:
result = newIntLit(lastOrd(skipTypes(n.typ, abstractVar)))
else:
result = newNodeI(nkCall, n.info, 2)
result.typ = getSysType(tyInt)
result.sons[0] = newSymNode(createMagic("high", mHigh))
result.sons[1] = n
proc setupArgsForParallelism(n: PNode; objType: PType; scratchObj: PSym;
castExpr, call, result: PNode) =
let formals = n[0].typ.n
let tmpName = getIdent(genPrefix)
for i in 1 .. <n.len:
let n = n[i]
let argType = skipTypes(if i < formals.len: formals[i].typ else: n.typ,
abstractInst)
if containsTyRef(argType):
localError(n.info, "'spawn'ed function cannot refer to 'ref'/closure")
let fieldname = if i < formals.len: formals[i].sym.name else: tmpName
var field = newSym(skField, fieldname, objType.owner, n.info)
if argType.kind in {tyVarargs, tyOpenArray}:
# important special case: we always create a zero-copy slice:
let slice = newNodeI(nkCall, n.info, 4)
slice.typ = n.typ
slice.sons[0] = newSymNode(createMagic("slice", mSlice))
var fieldB = newSym(skField, tmpName, objType.owner, n.info)
fieldB.typ = getSysType(tyInt)
objType.addField(fieldB)
if getMagic(n) == mSlice:
let a = genAddrOf(n[0])
field.typ = a.typ
objType.addField(field)
result.add newFastAsgnStmt(newDotExpr(scratchObj, field), a)
var fieldA = newSym(skField, tmpName, objType.owner, n.info)
fieldA.typ = getSysType(tyInt)
objType.addField(fieldA)
result.add newFastAsgnStmt(newDotExpr(scratchObj, fieldA), n[2])
result.add newFastAsgnStmt(newDotExpr(scratchObj, fieldB), n[3])
slice.sons[2] = indirectAccess(castExpr, fieldA, n.info)
else:
let a = genAddrOf(n)
field.typ = a.typ
objType.addField(field)
result.add newFastAsgnStmt(newDotExpr(scratchObj, field), a)
result.add newFastAsgnStmt(newDotExpr(scratchObj, fieldB), genHigh(n))
slice.sons[2] = newIntLit(0)
slice.sons[1] = genDeref(indirectAccess(castExpr, field, n.info))
slice.sons[3] = indirectAccess(castExpr, fieldB, n.info)
call.add slice
elif (let size = computeSize(argType); size < 0 or size > 16) and
n.getRoot != nil:
# it is more efficient to pass a pointer instead:
let a = genAddrOf(n)
field.typ = a.typ
objType.addField(field)
result.add newFastAsgnStmt(newDotExpr(scratchObj, field), a)
call.add(genDeref(indirectAccess(castExpr, field, n.info)))
else:
# boring case
field.typ = argType
objType.addField(field)
result.add newFastAsgnStmt(newDotExpr(scratchObj, field), n)
call.add(indirectAccess(castExpr, field, n.info))
proc wrapProcForSpawn*(owner: PSym; n: PNode; retType: PType;
barrier, dest: PNode = nil): PNode =
# if 'barrier' != nil, then it is in a 'parallel' section and we
# generate quite different code
let spawnKind = spawnResult(retType, barrier!=nil)
case spawnKind
of srVoid:
internalAssert dest == nil
result = newNodeI(nkStmtList, n.info)
of srFuture:
internalAssert dest == nil
result = newNodeIT(nkStmtListExpr, n.info, retType)
of srByVar:
if dest == nil: localError(n.info, "'spawn' must not be discarded")
result = newNodeI(nkStmtList, n.info)
if n.kind notin nkCallKinds:
localError(n.info, "'spawn' takes a call expression")
return return
if optThreadAnalysis in gGlobalOptions: if optThreadAnalysis in gGlobalOptions:
if {tfThread, tfNoSideEffect} * n[0].typ.flags == {}: if {tfThread, tfNoSideEffect} * n[0].typ.flags == {}:
@ -180,7 +398,7 @@ proc wrapProcForSpawn*(owner: PSym; n: PNode; barrier: PNode = nil): PNode =
varSectionB.addVar(scratchObj.newSymNode) varSectionB.addVar(scratchObj.newSymNode)
result.add varSectionB result.add varSectionB
var call = newNodeI(nkCall, n.info) var call = newNodeIT(nkCall, n.info, n.typ)
var fn = n.sons[0] var fn = n.sons[0]
# templates and macros are in fact valid here due to the nature of # templates and macros are in fact valid here due to the nature of
# the transformation: # the transformation:
@ -200,34 +418,10 @@ proc wrapProcForSpawn*(owner: PSym; n: PNode; barrier: PNode = nil): PNode =
call.add(fn) call.add(fn)
var varSection = newNodeI(nkVarSection, n.info) var varSection = newNodeI(nkVarSection, n.info)
let formals = n[0].typ.n if barrier.isNil:
let tmpName = getIdent(genPrefix) setupArgsForConcurrency(n, objType, scratchObj, castExpr, call, varSection, result)
for i in 1 .. <n.len: else:
# we pick n's type here, which hopefully is 'tyArray' and not setupArgsForParallelism(n, objType, scratchObj, castExpr, call, result)
# 'tyOpenArray':
var argType = n[i].typ.skipTypes(abstractInst)
if i < formals.len and formals[i].typ.kind == tyVar:
localError(n[i].info, "'spawn'ed function cannot have a 'var' parameter")
elif containsTyRef(argType):
localError(n[i].info, "'spawn'ed function cannot refer to 'ref'/closure")
let fieldname = if i < formals.len: formals[i].sym.name else: tmpName
var field = newSym(skField, fieldname, owner, n.info)
field.typ = argType
objType.addField(field)
result.add newFastAsgnStmt(newDotExpr(scratchObj, field), n[i])
var temp = newSym(skTemp, tmpName, owner, n.info)
temp.typ = argType
incl(temp.flags, sfFromGeneric)
var vpart = newNodeI(nkIdentDefs, n.info, 3)
vpart.sons[0] = newSymNode(temp)
vpart.sons[1] = ast.emptyNode
vpart.sons[2] = indirectAccess(castExpr, field, n.info)
varSection.add vpart
call.add(newSymNode(temp))
var barrierAsExpr: PNode = nil var barrierAsExpr: PNode = nil
if barrier != nil: if barrier != nil:
@ -239,7 +433,17 @@ proc wrapProcForSpawn*(owner: PSym; n: PNode; barrier: PNode = nil): PNode =
result.add newFastAsgnStmt(newDotExpr(scratchObj, field), barrier) result.add newFastAsgnStmt(newDotExpr(scratchObj, field), barrier)
barrierAsExpr = indirectAccess(castExpr, field, n.info) barrierAsExpr = indirectAccess(castExpr, field, n.info)
var futField, futAsExpr: PNode = nil
if spawnKind == srFuture:
var field = newSym(skField, getIdent"fut", owner, n.info)
field.typ = retType
objType.addField(field)
futField = newDotExpr(scratchObj, field)
futAsExpr = indirectAccess(castExpr, field, n.info)
let wrapper = createWrapperProc(fn, threadParam, argsParam, varSection, call, let wrapper = createWrapperProc(fn, threadParam, argsParam, varSection, call,
barrierAsExpr) barrierAsExpr, futAsExpr)
result.add callCodeGenProc("nimSpawn", wrapper.newSymNode, result.add callCodeGenProc("nimSpawn", wrapper.newSymNode,
genAddrOf(scratchObj.newSymNode)) genAddrOf(scratchObj.newSymNode))
if spawnKind == srFuture: result.add futField

View file

@ -115,6 +115,12 @@ proc semLocals(c: PContext, n: PNode): PNode =
if it.typ.skipTypes({tyGenericInst}).kind == tyVar: a = newDeref(a) if it.typ.skipTypes({tyGenericInst}).kind == tyVar: a = newDeref(a)
result.add(a) result.add(a)
proc createFuture(c: PContext; t: PType; info: TLineInfo): PType =
result = newType(tyGenericInvokation, c.module)
addSonSkipIntLit(result, magicsys.getCompilerProc("Future").typ)
addSonSkipIntLit(result, t)
result = instGenericContainer(c, info, result, allowMetaTypes = false)
proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode
proc magicsAfterOverloadResolution(c: PContext, n: PNode, proc magicsAfterOverloadResolution(c: PContext, n: PNode,
flags: TExprFlags): PNode = flags: TExprFlags): PNode =
@ -130,5 +136,9 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode,
of mShallowCopy: result = semShallowCopy(c, n, flags) of mShallowCopy: result = semShallowCopy(c, n, flags)
of mNBindSym: result = semBindSym(c, n) of mNBindSym: result = semBindSym(c, n)
of mLocals: result = semLocals(c, n) of mLocals: result = semLocals(c, n)
of mSpawn:
result = n
# later passes may transform the type 'Future[T]' back into 'T'
if not n[1].typ.isEmptyType:
result.typ = createFuture(c, n[1].typ, n.info)
else: result = n else: result = n

View file

@ -9,8 +9,8 @@
## Semantic checking for 'parallel'. ## Semantic checking for 'parallel'.
# - codegen needs to support mSlice # - codegen needs to support mSlice (+)
# - lowerings must not perform unnecessary copies # - lowerings must not perform unnecessary copies (+)
# - slices should become "nocopy" to openArray (+) # - slices should become "nocopy" to openArray (+)
# - need to perform bound checks (+) # - need to perform bound checks (+)
# #
@ -19,7 +19,7 @@
# - what about 'f(a)'? --> f shouldn't have side effects anyway # - what about 'f(a)'? --> f shouldn't have side effects anyway
# - passed arrays need to be ensured not to alias # - passed arrays need to be ensured not to alias
# - passed slices need to be ensured to be disjoint (+) # - passed slices need to be ensured to be disjoint (+)
# - output slices need special logic # - output slices need special logic (+)
import import
ast, astalgo, idents, lowerings, magicsys, guards, sempass2, msgs, ast, astalgo, idents, lowerings, magicsys, guards, sempass2, msgs,
@ -94,23 +94,6 @@ proc getSlot(c: var AnalysisCtx; v: PSym): ptr MonotonicVar =
c.locals[L].v = v c.locals[L].v = v
return addr(c.locals[L]) return addr(c.locals[L])
proc getRoot(n: PNode): PSym =
## ``getRoot`` takes a *path* ``n``. A path is an lvalue expression
## like ``obj.x[i].y``. The *root* of a path is the symbol that can be
## determined as the owner; ``obj`` in the example.
case n.kind
of nkSym:
if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar}:
result = n.sym
of nkDotExpr, nkBracketExpr, nkHiddenDeref, nkDerefExpr,
nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr:
result = getRoot(n.sons[0])
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
result = getRoot(n.sons[1])
of nkCallKinds:
if getMagic(n) == mSlice: result = getRoot(n.sons[1])
else: discard
proc gatherArgs(c: var AnalysisCtx; n: PNode) = proc gatherArgs(c: var AnalysisCtx; n: PNode) =
for i in 0.. <n.safeLen: for i in 0.. <n.safeLen:
let root = getRoot n[i] let root = getRoot n[i]
@ -184,8 +167,6 @@ proc overlap(m: TModel; x,y,c,d: PNode) =
of impNo: discard of impNo: discard
proc stride(c: AnalysisCtx; n: PNode): BiggestInt = proc stride(c: AnalysisCtx; n: PNode): BiggestInt =
# note: 0 if it cannot be determined is just right because then
# we analyse 'i..i' and 'i+0 .. i+0' and these are not disjoint!
if isLocal(n): if isLocal(n):
let s = c.lookupSlot(n.sym) let s = c.lookupSlot(n.sym)
if s >= 0 and c.locals[s].stride != nil: if s >= 0 and c.locals[s].stride != nil:
@ -193,6 +174,20 @@ proc stride(c: AnalysisCtx; n: PNode): BiggestInt =
else: else:
for i in 0 .. <n.safeLen: result += stride(c, n.sons[i]) for i in 0 .. <n.safeLen: result += stride(c, n.sons[i])
proc subStride(c: AnalysisCtx; n: PNode): PNode =
# substitute with stride:
if isLocal(n):
let s = c.lookupSlot(n.sym)
if s >= 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 .. <n.len: result.sons[i] = subStride(c, n.sons[i])
else:
result = n
proc checkSlicesAreDisjoint(c: var AnalysisCtx) = proc checkSlicesAreDisjoint(c: var AnalysisCtx) =
# this is the only thing that we need to perform after we have traversed # this is the only thing that we need to perform after we have traversed
# the whole tree so that the strides are available. # the whole tree so that the strides are available.
@ -200,7 +195,7 @@ proc checkSlicesAreDisjoint(c: var AnalysisCtx) =
addLowerBoundAsFacts(c) addLowerBoundAsFacts(c)
# Every slice used in a loop needs to be disjoint with itself: # Every slice used in a loop needs to be disjoint with itself:
for x,a,b,id,inLoop in items(c.slices): for x,a,b,id,inLoop in items(c.slices):
if inLoop: overlap(c.guards, a,b, a+@c.stride(a), b+@c.stride(b)) if inLoop: overlap(c.guards, a,b, c.subStride(a), c.subStride(b))
# Another tricky example is: # Another tricky example is:
# while true: # while true:
# spawn f(a[i]) # spawn f(a[i])
@ -283,23 +278,19 @@ proc analyseCall(c: var AnalysisCtx; n: PNode; op: PSym) =
proc analyseCase(c: var AnalysisCtx; n: PNode) = proc analyseCase(c: var AnalysisCtx; n: PNode) =
analyse(c, n.sons[0]) analyse(c, n.sons[0])
#let oldState = c.locals.len
let oldFacts = c.guards.len let oldFacts = c.guards.len
for i in 1.. <n.len: for i in 1.. <n.len:
let branch = n.sons[i] let branch = n.sons[i]
#setLen(c.locals, oldState)
setLen(c.guards, oldFacts) setLen(c.guards, oldFacts)
addCaseBranchFacts(c.guards, n, i) addCaseBranchFacts(c.guards, n, i)
for i in 0 .. <branch.len: for i in 0 .. <branch.len:
analyse(c, branch.sons[i]) analyse(c, branch.sons[i])
#setLen(c.locals, oldState)
setLen(c.guards, oldFacts) setLen(c.guards, oldFacts)
proc analyseIf(c: var AnalysisCtx; n: PNode) = proc analyseIf(c: var AnalysisCtx; n: PNode) =
analyse(c, n.sons[0].sons[0]) analyse(c, n.sons[0].sons[0])
let oldFacts = c.guards.len let oldFacts = c.guards.len
addFact(c.guards, n.sons[0].sons[0]) addFact(c.guards, n.sons[0].sons[0])
#let oldState = c.locals.len
analyse(c, n.sons[0].sons[1]) analyse(c, n.sons[0].sons[1])
for i in 1.. <n.len: for i in 1.. <n.len:
@ -309,10 +300,8 @@ proc analyseIf(c: var AnalysisCtx; n: PNode) =
addFactNeg(c.guards, n.sons[j].sons[0]) addFactNeg(c.guards, n.sons[j].sons[0])
if branch.len > 1: if branch.len > 1:
addFact(c.guards, branch.sons[0]) addFact(c.guards, branch.sons[0])
#setLen(c.locals, oldState)
for i in 0 .. <branch.len: for i in 0 .. <branch.len:
analyse(c, branch.sons[i]) analyse(c, branch.sons[i])
#setLen(c.locals, oldState)
setLen(c.guards, oldFacts) setLen(c.guards, oldFacts)
proc analyse(c: var AnalysisCtx; n: PNode) = proc analyse(c: var AnalysisCtx; n: PNode) =
@ -390,17 +379,40 @@ proc transformSlices(n: PNode): PNode =
else: else:
result = n 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 = proc transformSpawn(owner: PSym; n, barrier: PNode): PNode =
if n.kind in nkCallKinds: case n.kind
if n[0].kind == nkSym: of nkVarSection:
let op = n[0].sym result = nil
if op.magic == mSpawn: for it in n:
result = transformSlices(n) let b = it.lastSon
return wrapProcForSpawn(owner, result[1], barrier) 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
result.add wrapProcForSpawn(owner, m[1], b.typ, barrier, it[0])
it.sons[it.len-1] = emptyNode
if result.isNil: result = n
of nkAsgn, nkFastAsgn:
let b = n[1]
if getMagic(b) == mSpawn:
let m = transformSlices(b)
return wrapProcForSpawn(owner, m[1], b.typ, barrier, n[0])
result = transformSpawnSons(owner, n, barrier)
of nkCallKinds:
if getMagic(n) == mSpawn:
result = transformSlices(n)
return wrapProcForSpawn(owner, result[1], n.typ, barrier, nil)
result = transformSpawnSons(owner, n, barrier)
elif n.safeLen > 0: elif n.safeLen > 0:
result = shallowCopy(n) result = transformSpawnSons(owner, n, barrier)
for i in 0 .. < n.len:
result.sons[i] = transformSpawn(owner, n.sons[i], barrier)
else: else:
result = n result = n
@ -440,3 +452,4 @@ proc liftParallel*(owner: PSym; n: PNode): PNode =
result.add callCodeGenProc("openBarrier", barrier) result.add callCodeGenProc("openBarrier", barrier)
result.add transformSpawn(owner, body, barrier) result.add transformSpawn(owner, body, barrier)
result.add callCodeGenProc("closeBarrier", barrier) result.add callCodeGenProc("closeBarrier", barrier)

View file

@ -2748,7 +2748,7 @@ The following builtin procs cannot be overloaded for reasons of implementation
simplicity (they require specialized semantic checking):: simplicity (they require specialized semantic checking)::
defined, definedInScope, compiles, low, high, sizeOf, defined, definedInScope, compiles, low, high, sizeOf,
is, of, echo, shallowCopy, getAst is, of, echo, shallowCopy, getAst, spawn
Thus they act more like keywords than like ordinary identifiers; unlike a Thus they act more like keywords than like ordinary identifiers; unlike a
keyword however, a redefinition may `shadow`:idx: the definition in keyword however, a redefinition may `shadow`:idx: the definition in

View file

@ -65,6 +65,30 @@ proc closeBarrier*(b: ptr Barrier) {.compilerProc.} =
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
type type
AwaitInfo = object
cv: CondVar
idx: int
RawFuture* = ptr RawFutureObj ## untyped base class for 'Future[T]'
RawFutureObj {.inheritable.} = object # \
# we allocate this with the thread local allocator; this
# is possible since we already need to do the GC_unref
# on the owning thread
ready, usesCondVar: bool
cv: CondVar #\
# for 'awaitAny' support
ai: ptr AwaitInfo
idx: int
data: PObject # we incRef and unref it to keep it alive
owner: ptr Worker
next: RawFuture
align: float64 # a float for proper alignment
Future* {.compilerProc.} [T] = ptr object of RawFutureObj
blob: T ## the underlying value, if available. Note that usually
## you should not access this field directly! However it can
## sometimes be more efficient than getting the value via ``^``.
WorkerProc = proc (thread, args: pointer) {.nimcall, gcsafe.} WorkerProc = proc (thread, args: pointer) {.nimcall, gcsafe.}
Worker = object Worker = object
taskArrived: CondVar taskArrived: CondVar
@ -75,6 +99,92 @@ type
ready: bool # put it here for correct alignment! ready: bool # put it here for correct alignment!
initialized: bool # whether it has even been initialized initialized: bool # whether it has even been initialized
shutdown: bool # the pool requests to shut down this worker thread shutdown: bool # the pool requests to shut down this worker thread
futureLock: TLock
head: RawFuture
proc finished*(fut: RawFuture) =
## This MUST be called for every created future to free its associated
## resources. Note that the default reading operation ``^`` is destructive
## and calls ``finished``.
doAssert fut.ai.isNil, "future is still attached to an 'awaitAny'"
assert fut.next == nil
let w = fut.owner
acquire(w.futureLock)
fut.next = w.head
w.head = fut
release(w.futureLock)
proc cleanFutures(w: ptr Worker) =
var it = w.head
acquire(w.futureLock)
while it != nil:
let nxt = it.next
if it.usesCondVar: destroyCondVar(it.cv)
if it.data != nil: GC_unref(it.data)
dealloc(it)
it = nxt
w.head = nil
release(w.futureLock)
proc nimCreateFuture(owner: pointer; blobSize: int): RawFuture {.
compilerProc.} =
result = cast[RawFuture](alloc0(RawFutureObj.sizeof + blobSize))
result.owner = cast[ptr Worker](owner)
proc nimFutureCreateCondVar(fut: RawFuture) {.compilerProc.} =
fut.cv = createCondVar()
fut.usesCondVar = true
proc nimFutureSignal(fut: RawFuture) {.compilerProc.} =
assert fut.usesCondVar
signal(fut.cv)
proc await*[T](fut: Future[T]) =
## waits until the value for the future arrives.
if fut.usesCondVar: await(fut.cv)
proc `^`*[T](fut: Future[T]): T =
## blocks until the value is available and then returns this value. Note
## this reading is destructive for reasons of efficiency and convenience.
## This calls ``finished(fut)``.
await(fut)
when T is string or T is seq or T is ref:
result = cast[T](fut.data)
else:
result = fut.payload
finished(fut)
proc notify*(fut: RawFuture) {.compilerproc.} =
if fut.ai != nil:
acquire(fut.ai.cv.L)
fut.ai.idx = fut.idx
inc fut.ai.cv.counter
release(fut.ai.cv.L)
signal(fut.ai.cv.c)
if fut.usesCondVar: signal(fut.cv)
proc awaitAny*(futures: openArray[RawFuture]): int =
# awaits any of the given futures. Returns the index of one future for which
## a value arrived. A future only supports one call to 'awaitAny' at the
## same time. That means if you await([a,b]) and await([b,c]) the second
## call will only await 'c'. If there is no future left to be able to wait
## on, -1 is returned.
var ai: AwaitInfo
ai.cv = createCondVar()
var conflicts = 0
for i in 0 .. futures.high:
if cas(addr futures[i].ai, nil, addr ai):
futures[i].idx = i
else:
inc conflicts
if conflicts < futures.len:
await(ai.cv)
result = ai.idx
for i in 0 .. futures.high:
discard cas(addr futures[i].ai, addr ai, nil)
else:
result = -1
destroyCondVar(ai.cv)
proc nimArgsPassingDone(p: pointer) {.compilerProc.} = proc nimArgsPassingDone(p: pointer) {.compilerProc.} =
let w = cast[ptr Worker](p) let w = cast[ptr Worker](p)
@ -99,6 +209,7 @@ proc slave(w: ptr Worker) {.thread.} =
await(w.taskArrived) await(w.taskArrived)
assert(not w.ready) assert(not w.ready)
w.f(w, w.data) w.f(w, w.data)
if w.head != nil: w.cleanFutures
if w.shutdown: if w.shutdown:
w.shutdown = false w.shutdown = false
atomicDec currentPoolSize atomicDec currentPoolSize
@ -119,6 +230,7 @@ var
proc activateThread(i: int) {.noinline.} = proc activateThread(i: int) {.noinline.} =
workersData[i].taskArrived = createCondVar() workersData[i].taskArrived = createCondVar()
workersData[i].taskStarted = createCondVar() workersData[i].taskStarted = createCondVar()
initLock workersData[i].futureLock
workersData[i].initialized = true workersData[i].initialized = true
createThread(workers[i], slave, addr(workersData[i])) createThread(workers[i], slave, addr(workersData[i]))

View file

@ -209,12 +209,12 @@ when defined(windows) and not defined(gcc):
proc interlockedCompareExchange(p: pointer; exchange, comparand: int32): int32 proc interlockedCompareExchange(p: pointer; exchange, comparand: int32): int32
{.importc: "InterlockedCompareExchange", header: "<windows.h>", cdecl.} {.importc: "InterlockedCompareExchange", header: "<windows.h>", cdecl.}
proc cas*[T: bool|int](p: ptr T; oldValue, newValue: T): bool = proc cas*[T: bool|int|ptr](p: ptr T; oldValue, newValue: T): bool =
interlockedCompareExchange(p, newValue.int32, oldValue.int32) != 0 interlockedCompareExchange(p, newValue.int32, oldValue.int32) != 0
# XXX fix for 64 bit build
else: else:
# this is valid for GCC and Intel C++ # this is valid for GCC and Intel C++
proc cas*[T: bool|int](p: ptr T; oldValue, newValue: T): bool proc cas*[T: bool|int|ptr](p: ptr T; oldValue, newValue: T): bool
{.importc: "__sync_bool_compare_and_swap", nodecl.} {.importc: "__sync_bool_compare_and_swap", nodecl.}
# XXX is this valid for 'int'? # XXX is this valid for 'int'?

View file

@ -1,20 +1,20 @@
discard """
outputsub: "EVEN 28"
"""
import threadpool import threadpool
proc f(a: openArray[int]) = proc odd(a: int) = echo "ODD ", a
for x in a: echo x proc even(a: int) = echo "EVEN ", a
proc f(a: int) = echo a
proc main() = proc main() =
var a: array[0..30, int] var a: array[0..30, int]
for i in low(a)..high(a): a[i] = i
parallel: parallel:
#spawn f(a[0..15])
#spawn f(a[16..30])
var i = 0 var i = 0
while i <= 29: while i <= 29:
spawn f(a[i]) spawn even(a[i])
spawn f(a[i+1]) spawn odd(a[i+1])
inc i, 2 inc i, 2
# is correct here # is correct here

View file

@ -1,5 +1,5 @@
discard """ discard """
errormsg: "cannot prove: i + 1 <= 30" errormsg: "can prove: i + 1 > 30"
line: 21 line: 21
""" """