diff --git a/hpvm/docs/hpvm-specification.md b/hpvm/docs/hpvm-specification.md
index 360cf106e63c46387484aa755a93d53a2f02813d..157e57452aedd94d2b06b56958d44c07ede17fed 100644
--- a/hpvm/docs/hpvm-specification.md
+++ b/hpvm/docs/hpvm-specification.md
@@ -1,5 +1,5 @@
 # HPVM Abstraction
-An HPVM program is a combination of host code plus a set of one or more distinct dataflow graphs. Each dataflow graph (DFG) is a hierarchical graph with side effects. Nodes represent units of execution, and edges between nodes describe the explicit data transfer requirements. A node can begin execution once a data item becomes available on every one of its input edges. Repeated transfer of data items between nodes (if more inputs are provided) yields a pipelined execution of different nodes in the graph. The execution of a DFG is initiated and terminated by host code that launches the graph. Nodes may access globally shared memory through load and store instructions (side-effects).
+An HPVM program is a combination of host code plus a set of one or more distinct dataflow graphs. Each dataflow graph (DFG) is a hierarchical graph with side effects. The DFG must be acyclic. Nodes represent units of execution, and edges between nodes describe the explicit data transfer requirements. A node can begin execution once a data item becomes available on every one of its input edges. Repeated transfer of data items between nodes (if more inputs are provided) yields a pipelined execution of different nodes in the graph. The execution of a DFG is initiated and terminated by host code that launches the graph. Nodes may access globally shared memory through load and store instructions (side-effects).
 
 ## Dataflow Node
 A *dataflow node* represents unit of computation in the DFG. A node can begin execution once a data item becomes available on every one of its input edges.
@@ -90,13 +90,13 @@ Create a static dataflow node replicated in two dimensions, namely ```x``` and `
 Create a static dataflow node replicated in three dimensions, namely ```x```, ```y``` and ```z```, with ```n1```, ```n2``` and ```n3``` dynamic instances in each dimension respectively, executing node function ```F```. Return a handle to the created node.
 
 ```i8* llvm.hpvm.createEdge(i8* Src, i8* Dst, i1 ReplType, i32 sp, i32 dp, i1 isStream)```  
-Create edge from output ```sp``` of node ```Src``` to input ```dp``` of node ```Dst```. ```ReplType``` chooses between a one-to-one (0) or all-to-all (1) edge. ```isStream``` chooses a streaming (1) or non streaming (0) edge. Return a handle to the created edge.
+Create edge from output ```sp``` of node ```Src``` to input ```dp``` of node ```Dst```. Argument ```dp``` of ```Dst```'s node function and field ```sp``` of the return struct in ```Src```'s node function must have matching types. ```ReplType``` chooses between a one-to-one (0) or all-to-all (1) edge. ```isStream``` chooses a streaming (1) or non streaming (0) edge. Return a handle to the created edge.
 
 ```void llvm.hpvm.bind.input(i8* N, i32 ip, i32 ic, i1 isStream)```  
-Bind input ```ip``` of current node to input ```ic``` of child node ```N```. ```isStream``` chooses a streaming (1) or non streaming (0) bind.
+Bind input ```ip``` of current node to input ```ic``` of child node ```N```. Argument ```ic``` of ```N```'s node function and argument ```ip``` of the current node function must have matching types. ```isStream``` chooses a streaming (1) or non streaming (0) bind.
 
 ```void llvm.hpvm.bind.output(i8* N, i32 oc, i32 op, i1 isStream)```  
-Bind output ```oc``` of child node ```N``` to output ```op``` of current node. ```isStream``` chooses a streaming (1) or non streaming (0) bind.
+Bind output ```oc``` of child node ```N``` to output ```op``` of current node. Field ```oc``` of the return struct in ```N```'s node function and field ```op``` of the return struct in the current node function must have matching types. ```isStream``` chooses a streaming (1) or non streaming (0) bind.
 
 ## Intrinsics for Querying Graphs
 
@@ -172,7 +172,7 @@ Stop tracking memory object with key ```ptr```, and remove it from memory tracke
 If memory object with key ```ptr``` is not located in host memory, copy it to host memory.
 
 ```i8* llvm.hpvm.launch(i8* RootGraph, i8* Args, i1 isStream)```  
-Launch the execution of DFG with node function ```RootGraph```. ```Args``` is a pointer to packed struct, containing one field per argument of the ```RootGraph``` function, consecutively. For non-streaming DFGs with a non empty result type, ```Args``` must contain an additional field of the type ```RootGraph.returnTy```, where the result of the graph will be returned. ```isStream``` chooses between a non streaming (0) or streaming (1) graph execution. Return a handle to the invoked DFG.
+Launch the execution of a top-level DFG with root node function ```RootGraph```. ```Args``` is a pointer to a packed struct, containing one field per argument of the ```RootGraph``` function, consecutively. For non-streaming DFGs with a non empty result type, ```Args``` must contain an additional field of the type ```RootGraph.returnTy```, where the result of the graph will be returned. ```isStream``` chooses between a non streaming (0) or streaming (1) graph execution. Return a handle to the invoked DFG.
 
 ```void llvm.hpvm.wait(i8* GraphID)```  
 Wait for completion of execution of DFG with handle ```GraphID```.
@@ -181,9 +181,10 @@ Wait for completion of execution of DFG with handle ```GraphID```.
 Push set of input data ```args``` (same as type included in launch) to streaming DFG with handle ```GraphID```.
 
 ```i8* llvm.hpvm.pop(i8* GraphID)```  
-Pop  and return data from streaming DFG with handle ```GraphID```.
+Pop and return data from streaming DFG with handle ```GraphID```. The return type is a struct containing a field for every output of DFG. 
 
 ## Implementation Limitations
 Due to limitations of our current prototype implementation, the following restrictions are imposed:
 - In HPVM, a memory object is represented as a (pointer, size) pair that includes the address of memory object, and the size (in bytes) of the pointed-to object. Therefore, when an edge/bind carries a pointer, it must be followed by an i64 size value.           
 - Pointers cannot be transferred between nodes using dataflow edges. Instead, they should be passed using the bind operation from the (common) parent of the source and sink nodes.
+- Instantiation of dataflow nodes is supported in up to three dimensions.