Skip to content

Commit 8c9f856

Browse files
committed
incremental compilation
1 parent 08c2d05 commit 8c9f856

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
2+
# Incremental Compilation
3+
Any time a NodeInput is changed in the network the following changes to the borrow tree are necessary:
4+
5+
1. Insert the new upstream nodes into the borrow tree based on their SNI
6+
2. Remove upstream SNI's which are now orphaned
7+
3. Update the SNI of the downstream nodes (Not necessary if ghostcell is used and downstream nodes can have their inputs mutated)
8+
4. Reset any downstream caches
9+
10+
We currently clone and recompile the entire network, which is very expensive. As networks grow larger and functionality gets split into separate nodes, a reasonable network may have hundreds of thousands of nodes. I believe with the following data structure and algorithm changes, it will be possible to implement incremental compilation, where a diff of changes is sent to the borrow tree. The overall goal of this method is the editor "damages" the node network by performing a series of set_input requests that invalidate downstream SNI's, and get queued in the compiler. Then, a compilation request iterates over the network, and uses the input requests, global cached compilation metadata,and NODE_REGISTRY to send a diff of updates to to the executor. These updates are `Add(SNI, (Constructor, Vec<SNI>))` and `Remove (SNI)` requests. In the future, GhostCell could be used to make a new `Modify((SNI, usize), SNI)` request, which remaps a nodes input to a new SharedNodeContainer.
11+
12+
# New Editor Workflow
13+
Each document still stores a NodeNetwork, but this is used just for serialization. When a document is opened, the NodeNetwork gets moved into the compiler (within NodeId(0), the render node is NodeId(1), and stays the same). When the document is closed, the network within NodeId(0) is taken from the compiler and moved into the document. All changes such as add node, remove node, and set input get sent to the compiler. Then on a compilation request, the set input requests are applied, the NodeNetwork is modified, and the diff of changes is generated to be applied to the borrow tree.
14+
15+
# Editor changes:
16+
The editor now performs all compilation, type resolution, and stable node id generation.
17+
18+
```rust
19+
struct SNI(u64);
20+
type ProtonodeInput = (Vec<NodeId>, usize);
21+
```
22+
SNI represents the stable node id of a protonode, which is calculated based on the hash of the inputs SNI + implementation string, or the hash of the value. Protonode SNI's may become stale, in which case the counter in CompiledProtonodeMetadata is incremented
23+
24+
ProtonodeInput is the path to the protonode DocumentNode in the recursive NodeNetwork structure, as well as the input index.
25+
26+
```rust
27+
PortfolioMessageHandler {
28+
pub compiler_metadata: HashMap<SNI, CompilerMetadata>
29+
}
30+
31+
// Used by the compiler and editor to send diff based changes to the runtime and cache the types so the constructor can be easily looked up
32+
pub struct NodeNetworkCompiler {
33+
resolved_type: NodeIOTypes,
34+
// How many document nodes with this SNI exist. Could possibly be moved to the executor
35+
usages: usize,
36+
// The previously compiled NodeNetwork, which represents the current state of the borrow tree.
37+
network: NodeNetwork
38+
// A series of SetInput requests which are queued between compilations
39+
input_requests: HashMap<InputConnector, NodeInput>
40+
}
41+
```
42+
43+
The portfolio message handler stores a mapping of types for each SNI in the borrow tree. This is shared across documents, which also share a runtime. It represents which nodes already exist in the borrow tree, and thus can be replaced with ProtonodeEntry::Existing(SNI). It also stores the usages of each SNI. If it drops to zero after a compilation, then it is removed from the borrow tree.
44+
45+
46+
```rust
47+
DocumentNodeImplementation::ProtoNode(DocumentProtonode)
48+
49+
enum DocumentProtonode {
50+
stable_node_id: Option<SNI>,
51+
cache_output: bool,
52+
identifier: ProtonodeIdentifier,
53+
// Generated during compile time, and used to unload downstream SNI's which relied on this protonode
54+
callers: Vec<ProtonodePath>,
55+
}
56+
57+
```
58+
59+
The editor protonode now stores its SNI. This is used to get its type information, thumbnail information, and other metadata during compilation. It is set during compilation. If it already exists, then compilation can skip any upstream nodes. This is similar to the MemoHash for value inputs, which is also changed to follow the same pattern for clarity.
60+
61+
Cache protonodes no longer exist, instead the protonode is annotated to insert a cache on its output. The current cache node can be replaced with an identity node with this field set to true. This field is set to true during compilation whenever the algorithm decides to add a cache node. Currently, this is whenever context nullification is added, but more advanced rules can be implemented. The overall goal is to add a cache node when a protonode has a high likely hood of being called multiple times with the same Context, takes a long time to evaluate, and returns a small amount of data. A similar algorithm could also be used to be determine when an input should be evaluated in another thread.
62+
63+
```rust
64+
pub enum NodeInput {
65+
Value { ValueInput },
66+
...
67+
}
68+
69+
pub struct ValueInput {
70+
value: TaggedValue,
71+
stable_node_id: SNI,
72+
...metadata
73+
}
74+
```
75+
This is a simple rename to change the current memo hash to SNI, which makes it more clear that it is the same concept as for protonodes.
76+
77+
# Editor -> Runtime changes
78+
These structs are used to transfer data from the editor to the runtime in CompilationRequest. Eventually the goal is move the entire NodeNetwork into the runtime, in which case the editor will send requests such as AddNode and SetInput. Then it will send queries to get data from the network.
79+
80+
```rust
81+
pub struct RuntimeUpdate {
82+
// Topologically sorted
83+
nodes: Vec<ProtonodeUpdate>,
84+
}
85+
```
86+
This represents the compiled proto network which is sent to the runtime and used to update borrow tree
87+
88+
```rust
89+
pub enum ProtonodeUpdate {
90+
// A new SNI that does not exist in the borrow tree or in the ProtoNetwork
91+
NewProtonode(SNI, ConstructionArgs),
92+
// A protonode's SNI already exists in the protonetwork update
93+
Deduplicated,
94+
// Remove a node from the borrow tree when it has no callers
95+
Remove(SNI)
96+
}
97+
```
98+
99+
Used to transfer information from the editor to the runtime.
100+
101+
```rust
102+
pub enum ConstructionArgs {
103+
/// A value of a type that is known, allowing serialization (serde::Deserialize is not object safe)
104+
Value(TaggedValue),
105+
Nodes(NodeConstructionArgs),
106+
/// Used for GPU computation to work around the limitations of rust-gpu.
107+
Inline(InlineRust),
108+
}
109+
```
110+
Im not sure what Inline is
111+
112+
```rust
113+
pub Struct NodeConstructionArgs {
114+
//used to get the constructor
115+
pub type: NodeIOTypes,
116+
pub id: ProtonodeIdentifier,
117+
/// A mapping of each input to the SNI, used to get the upstream SharedNodeContainers for the constructor
118+
inputs: Vec<SNI>,
119+
}
120+
```
121+
The goal of NodeConstruction args is to create the SharedNodeContainer that can be inserted into the borrow tree. It does this by using the id/types to get the implementation constructor, then using the vec of input SNI's to get references to the upstream inserted nodes.
122+
123+
124+
## Runtime Changes
125+
The runtime only contains the borrow tree, which has to have some way of iterating in topological order from any point.
126+
```rust
127+
pub struct BorrowTree {
128+
nodes: HashMap<SNI, (SharedNodeContainer,NodeConstructor)>,
129+
}
130+
131+
The SNI is used to perform the correct updates/introspection to the borrow tree by the editor. It doesnt have to be correct, it just has to match the editor state.
132+
133+
134+
# Full Compilation Process
135+
136+
Every time an input changes in the editor, it first performs a downstream traversal using the callers field in the protonode, and sets all Downstream SNI's to None. It also decrements the usages of that SNI by one. If it reaches 0, then it must no longer exist since there has been an upstream change, so its SNI must have changed. Save a vec of SNI to the protonetwork. It then performs an upstream traversal to create a topologically sorted ProtoNetwork. This would ideally be done non recursively, as networks are typically very deep.
137+
138+
The traversal starts at the root export, and gets the SNI of all inputs. gets the SNI of the upstream node. If it is None, then continue traversal. When coming up the callstack, compute the SNI based on the inputs and compute/save the type if it is not saved. Then, increment the usages by one.
139+
If the type already exists, then add a Deduplicated entry to the network. Continue to the root.
140+
141+
Next, iterate over the old input and decrement all usages by one. If it reaches 0, push Remove(SNI) to the protonetwork. Finally, push the saved vec of downstream nodes to remove.
142+
143+
it is now sent to the runtime which inserts or removes the corresponding nodes.
144+
145+
146+
Overall Benefits:
147+
-Compilation is now performed in the editor, which means typing information/SNI generation is all moved into the editor, and nothing has to be returned.
148+
149+
- NodeNetwork doesnt have to be hashed/cloned for every compilation request. It doesnt have to be hashed, since every set compilation request is guaranteed to change the network.
150+
151+
- Much easier to keep runtime in sync and prevent unnecessary compilations since compilation requests don't have to be manually added
152+
153+
- The mirrored metadata structure in network interface can be removed, and all metadata can be stored in `DocumentNode`/`NodeInput`, as they are now editor only.
154+
155+
- Compilation only has to be performed on nodes which do not have an SNI, and can stop early if the SNI already exists in `compiler_metadata`
156+
157+
-Undo history is merged with the compiler functionality. Undoing a network reverts to the previously compiled network. We no longer have to manually add/start transactions, instead just

0 commit comments

Comments
 (0)