-- Copyright (c) 2009, ERICSSON AB-- All rights reserved.---- Redistribution and use in source and binary forms, with or without-- modification, are permitted provided that the following conditions are met:---- * Redistributions of source code must retain the above copyright notice,-- this list of conditions and the following disclaimer.-- * Redistributions in binary form must reproduce the above copyright-- notice, this list of conditions and the following disclaimer in the-- documentation and/or other materials provided with the distribution.-- * Neither the name of the ERICSSON AB nor the names of its contributors-- may be used to endorse or promote products derived from this software-- without specific prior written permission.---- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.-- | A graph representation of core programs. A graph is a flat structure that-- can be viewed as a program with a global scope. For example, the Haskell-- program---- > main x = f 1-- > where-- > f y = g 2-- > where-- > g z = x + z---- might be represented by the following flat graph:---- > graph = Graph-- > { graphNodes =-- > [ Node-- > { nodeId = 0-- > , function = Input-- > , input = Tup []-- > , inputType = Tup []-- > , outputType = intType-- > }-- > , Node-- > { nodeId = 1-- > , function = Input-- > , input = Tup []-- > , inputType = Tup []-- > , outputType = intType-- > }-- > , Node-- > { nodeId = 2-- > , function = Input-- > , input = Tup []-- > , inputType = Tup []-- > , outputType = intType-- > }-- > , Node-- > { nodeId = 3-- > , function = Function "(+)"-- > , input = Tup [One (Variable (0,[])), One (Variable (2,[]))]-- > , inputType = intPairType-- > , outputType = intType-- > }-- > , Node-- > { nodeId = 4-- > , function = NoInline "f" (Interface 1 (One (Variable (5,[]))) intType intType)-- > , input = One (Constant (IntData 1))-- > , inputType = intType-- > , outputType = intType-- > }-- > , Node-- > { nodeId = 5-- > , function = NoInline "g" (Interface 2 (One (Variable (3,[]))) intType intType)-- > , input = One (Constant (IntData 2))-- > , inputType = intType-- > , outputType = intType-- > }-- > ]-- >-- > , graphInterface = Interface-- > { interfaceInput = 0-- > , interfaceOutput = One (Variable (4,[]))-- > , interfaceInputType = intType-- > , interfaceOutputType = intType-- > }-- > }-- > where-- > intType = result (typeOf :: Res [[[Int]]] (Tuple StorableType))-- > intPairType = result (typeOf :: Res (Int,Int) (Tuple StorableType))---- which corresponds to the following flat program---- > main v0 = v4-- > f v1 = v5-- > g v2 = v3-- > v3 = v0 + v2-- > v4 = f 1-- > v5 = g 2---- There are a few assumptions on graphs:---- * All nodes have unique identifiers.---- * There are no cycles.---- * The 'input' and 'inputType' tuples of each node should have the same shape.---- * Each 'interfaceInput' (including the top-level one) refers to an 'Input'-- node not referred to by any other interface.---- * All 'Variable' references are valid (i.e. refer only to those variables-- implicitly defined by each node).---- * There should not be any cycles in the constraints introduced by-- 'findLocalities'. (XXX Is this even possible?)---- * Sub-function interfaces should be \"consistent\" with the input/output type-- of the node. For example, the body of a while loop should have the same type-- as the whole loop.---- In the original program, @g@ was defined locally to @f@, and the addition was-- done locally in @g@. But in the flat program, this hierarchy (called-- /definition hierarchy/) is not represented. The flat program is of course not-- valid Haskell (@v0@ and @v2@ are used outside of their scopes). The function-- 'makeHierarchical' turns a flat graph into a hierarchical one that-- corresponds to syntactically valid Haskell.---- 'makeHierarchical' requires some explanation. First a few definitions:---- * Nodes that have associated interfaces ('NoInline', 'IfThenElse', 'While'-- and 'Parallel') are said to contain /sub-functions/. These nodes are called-- /super nodes/. In the above program, the super node @v4@ contains the-- sub-function @f@, and @v5@ contains the sub-function @g@.---- * A definition @d@ is /local/ to a definition @e@ iff. @d@ is placed-- somewhere within the definition of @e@ (i.e. inside an arbitrarily deeply-- nested @where@ clause).---- * A definition @d@ is /owned/ by a definition @e@ iff. @d@ is placed-- immediately under the top-most @where@ clause of @e@. A definition may have-- at most one owner.---- The definition hierarchy thus specifies ownership between the definitions in-- the program. There are two types of ownership:---- * A super node is always the owner of its sub-functions.---- * A sub-function may be the owner of some node definitions.---- Assigning nodes to sub-functions in a useful way takes some work. It is done-- by first finding out for each node which sub-functions it must be local to.-- Each locality constraint gives an upper bound on where in the definition-- hierarchy the node may be placed. There is one principle for introducing a-- locality constraint:---- * If node @v@ depends on the input of sub-function @f@, then @v@ must be-- local to @f@.---- The locality constraints for a graph can thus be found be tracing each-- sub-function input in order to find the nodes that depend on it (see function-- 'findLocalities'). In the above program, we have the sub-functions @f@ and-- @g@ with the inputs @v1@ and @v2@ respectively. We can see immediately that-- no node depends on @v1@, so we get no locality constraints for @f@. The only-- node that depends on @v2@ is @v3@, so the program has a single locality-- constraint: @v3@ is local to @g@. Nodes without constraints are simply taken-- to be local to @main@. With this information, we can now rewrite the flat-- program as---- > main v0 = v4-- > where-- > v4 = f 1-- > where-- > f v1 = v5-- > v5 = g 2-- > where-- > g v2 = v3-- > where-- > v3 = v0 + v2---- which is syntactically valid Haskell. Note that this program is slightly-- different from the original which defined @g@ locally to @f@. However, in-- general, we want definitions to be as \"global\" as possible in order to-- maximize sharing. For example, we don't want to put definitions in the body-- of a while loop unless they really depend on the loop state, because then-- they will (probably, depending on implementation) be recomputed in every-- iteration. Also note that in this program, it is not strictly necessary to-- have the sub-functions owned by their super nodes -- @f@ and @g@ could have-- been owned by @main@ instead. However, this would cause clashes if two-- sub-functions have the same name. Having sub-functions owned by their super-- nodes is also a way of keeping related definitions together in the program.---- There is one caveat with the above method. Consider the following flat-- program:---- > main v0 = v4-- > f v1 = v5-- > g v2 = v3-- > v3 = v1 + 2-- > v4 = f 0-- > v5 = g 1---- Here, we get the locality constraint: @v3@ is local to @f@. However, to get a-- valid definition hierarchy, we also need @v5@ to be local to @f@. This is-- because @v5@ is the owner of @g@, and the output of @g@ is local to @f@. So-- when looking for dependencies, we should let each super node depend on its-- sub-function output, /except/ for the owner of the very sub-function that is-- being traced (a function cannot be owned by itself).moduleFeldspar.Core.GraphwhereimportqualifiedData.FoldableasFoldimportData.FunctionimportData.ListimportData.Map(Map)importqualifiedData.MapasMapimportFeldspar.UtilsimportFeldspar.Core.Types-- | Node identifiertypeNodeId=Int-- | Variable represented by a node id and a tuple path. For example, in a-- definition (given in Haskell syntax)---- > ((a,b),c) = f x---- the variable @b@ would be represented as @(i,[0,1])@ (where @i@ is the id of-- the @f@ node).typeVariable=(NodeId,[Int])-- | The source of a value is either constant data or a variable.dataSource=ConstantPrimitiveData|VariableVariablederiving(Eq,Show)-- | A node in the program graph. The input is given as a 'Source' tuple. The-- output is implicitly defined by the 'nodeId' and the 'outputType'. For-- example, a node with id @i@ and output type---- > Tup [One ..., One ...]---- has the implicit output---- > Tup [One (i,[0]), One (i,[1])]dataNode=Node{nodeId::NodeId,function::Function,input::TupleSource,inputType::TupleStorableType,outputType::TupleStorableType}deriving(Eq,Show)-- | The interface of a (sub-)graph. The input is conceptually a-- @Tuple Variable@, but all these variables refer to the same 'Input' node, so-- it is sufficient to track the node id (the tuple shape can be inferred from-- the 'interfaceInputType').dataInterface=Interface{interfaceInput::NodeId,interfaceOutput::TupleSource,interfaceInputType::TupleStorableType,interfaceOutputType::TupleStorableType}deriving(Eq,Show)-- | Node functionalitydataFunction=-- | Primary inputInput-- | Constant array|ArrayStorableData-- | Primitive function|FunctionString-- | Non-inlined function|NoInlineStringInterface-- | Conditional|IfThenElseInterfaceInterface-- | While-loop|WhileInterfaceInterface-- | Parallel tiling|ParallelIntInterfacederiving(Eq,Show)-- | A graph is a list of unique nodes with an interface.dataGraph=Graph{graphNodes::[Node],graphInterface::Interface}instanceEqGraphwhereGraphns1iface1==Graphns2iface2=ns1'==ns2'&&iface1==iface2wherens1'=sortBy(compare`on`nodeId)ns1ns2'=sortBy(compare`on`nodeId)ns2-- Comparison ignores order of nodes.-- | A definition hierarchy. A hierarchy consists of number of top-level nodes,-- each one associated with its sub-functions, represented as hierarchies. The-- nodes owned by a sub-function appear as the top-level nodes in the-- corresponding hierarchy.dataHierarchy=Hierarchy[(Node,[Hierarchy])]-- | A graph with a hierarchical ordering of the nodes. If the hierarchy is-- flattened it should result in a valid 'Graph'.dataHierarchicalGraph=HierGraph{graphHierarchy::Hierarchy,hierGraphInterface::Interface}-- | A node that contains a sub-functiontypeSuperNode=NodeId-- | The branch is used to distinguish between different sub-functions of the-- same super node. For example, the continue condition of a while-loop has-- branch number 0, and the body has number 1 (see 'subFunctions').dataSubFunction=SubFunction{sfSuper::SuperNode,sfBranch::Int,sfInput::NodeId,sfOutput::[NodeId]}deriving(Eq,Show)instanceOrdSubFunctionwherecompare(SubFunctiono1b1__)(SubFunctiono2b2__)=compare(o1,b1)(o2,b2)-- Ignores inputs/outputs since these should be equal anyway if the super-- and branch fields are equal.-- | Locality constraintdataLocal=LocalSubFunctionNodeIdderiving(Eq,Show)-- | Returns the nodes in a source tuple.sourceNodes::TupleSource->[NodeId]sourceNodestup=[i|Variable(i,_)<-Fold.toListtup]-- | The fanout of each node in a graph. Nodes that are not in the map are-- assumed to have no fanout.fanout::Graph->MapNodeId[NodeId]fanoutgraph=Map.fromListWith(++)[(inp,[nodeIdnode])|node<-graphNodesgraph,inp<-sourceNodes(inputnode)]-- | Look up a node in the graphnodeMap::Graph->(NodeId->Node)nodeMapgraph=(mMap.!)wherem=Map.fromList[(nodeIdnode,node)|node<-graphNodesgraph]-- | Lists all sub-functions in the graph.subFunctions::Graph->[SubFunction]subFunctionsgraph=concat[subFunifun|Nodeifun___<-graphNodesgraph]wheresubibranch(Interfaceinpoutp__)=SubFunctionibranchinp(sourceNodesoutp)subFuni(NoInline_f)=[subi0f]subFuni(IfThenElsete)=[subi0t,subi1e]subFuni(Whilecontbody)=[subi0cont,subi1body]subFuni(Parallel_ixf)=[subi0ixf]subFun__=[]-- | Lists all locality constraints of the graph.findLocalities::Graph->[Local]findLocalitiesgraph=concatMaptraceSubsfswherefo=fanoutgraphsfs=subFunctionsgraphsuperLink=Map.fromListWith(++)[(outp,[super])|SubFunctionsuper__outps<-sfs,outp<-outps]-- Fanout map with edges from sub-function output to super nodetraceSubsf@(SubFunction__inpoutps)=traceinpwheretracea=Localsfa:concatMaptracebswhereas=ifa`elem`outpsthen[]elsesuperLink!!!abs=(fo!!!a)++as-- Computes locality constraints by tracing the dependencies of-- sub-function inputs.-- | Returns a total ordering between all super nodes in a graph, such that if-- node @v@ is local to sub-function @f@, then @v@ maps to a lower number than-- the owner of @f@. The converse is not necessarily true. The second argument-- gives the locality constraints for each node in the graph (top-level nodes-- may be left undefined).orderSuperNodes::Graph->MapNodeId[SubFunction]->MapSuperNodeIntorderSuperNodesgraphlocals=Map.fromList$zip(topSortsfOrder)[0..]wheresfOrder=Map.fromListWith(++)[(i,mapsfSuper(locals!!!i))|SubFunctioni___<-subFunctionsgraph]-- A partial ordering between all sub-functions. An edge from `f` to `g`-- means that `f` is local to `g`. This is a representation of the actual-- sub-function ordering which is the transitive closure of `sfOrder`.-- `sfOrder` is a dag.-- | Returns the minimal sub-function according to the given owner ordering.minimalSubFun::MapSuperNodeInt->[SubFunction]->SubFunctionminimalSubFunownOrd=head.sortBy(compare`on`((ownOrdMap.!).sfSuper))-- | Sorts the nodes by their id.sortNodes::[Node]->[Node]sortNodes=sortBy(compare`on`nodeId)-- | Makes a hierarchical graph from a flat one. The node lists in the hierarchy-- are always sorted according to node id.makeHierarchical::Graph->HierarchicalGraphmakeHierarchicalgraph@(Graphnodesiface)=HierGraph(mkHierarchytopLevel)ifacewherelocs=findLocalitiesgraphlocals::MapNodeId[SubFunction]locals=Map.fromListWith(++)[(i,[sf])|Localsfi<-locs]-- The locality constraints for each node. Nodes that are not in the map-- have no constraints.owner::MapNodeIdSubFunctionowner=fmap(minimalSubFun$orderSuperNodesgraphlocals)locals-- The owner of each node. Nodes that are not in the map have no owner.nodeLookup::NodeId->NodenodeLookup=nodeMapgraphmkHierarchy::[Node]->HierarchymkHierarchynodes=Hierarchy(nodes`zip`mapsubHierarchiesnodes)subFunHier::SuperNode->Int->HierarchysubFunHieribranch=mkHierarchynodeswhereownedBy=fmap(sortNodes.mapnodeLookup)$invertMapownersf=SubFunctionibranchundefinedundefinednodes=ownedByMap.!sf-- Defined for every sub-function, because each sub-function contains-- at least one node (the input).subHierarchies::Node->[Hierarchy]subHierarchies(Nodei(NoInline__)___)=map(subFunHieri)[0]subHierarchies(Nodei(IfThenElse__)___)=map(subFunHieri)[0,1]subHierarchies(Nodei(While__)___)=map(subFunHieri)[0,1]subHierarchies(Nodei(Parallel__)___)=map(subFunHieri)[0]subHierarchies_=[]topLevel::[Node]topLevel=sortNodes[nodeLookupi|node<-nodes,leti=nodeIdnode,Nothing<-[Map.lookupiowner]]-- The nodes that don't have any owner