function getExecutionSubgraph(inputs, outputs, weightMap, initNodes) {
    const usedNodes = new Set();
    const missingInputs = [];
    let dynamicNode = null;
    let syncInputs = null;
    // Start with the outputs, going backwards and find all the nodes that are
    // needed to compute those outputs.
    const seen = new Set();
    const inputNodeNames = new Set(Object.keys(inputs).map((name)=>(0, _utils.parseNodeName)(name)[0]));
    initNodes = initNodes || [];
    const initNodeNames = new Set(initNodes.map((node)=>(0, _utils.parseNodeName)(node.name)[0]));
    const frontier = [
        ...outputs
    ];
    while(frontier.length > 0){
        const node = frontier.pop();
        if (isControlFlow(node) || isDynamicShape(node) || isHashTable(node)) {
            if (dynamicNode == null) {
                dynamicNode = node;
                syncInputs = dynamicNode.children.map((child)=>child.name).filter((name)=>usedNodes.has(name));
            }
        }
        usedNodes.add(node.name);
        // Weights are dead end since we already have their values.
        if (weightMap[node.name] != null) continue;
        // This node is a dead end since it's one of the user-provided inputs.
        if (inputNodeNames.has(node.name)) continue;
        // This node is a dead end since it doesn't have any inputs.
        if (initNodeNames.has(node.name)) continue;
        if (node.inputs.length === 0) {
            missingInputs.push(node.name);
            continue;
        }
        node.inputs.forEach((input)=>{
            // Don't add to the frontier if it is already there.
            if (seen.has(input.name)) return;
            seen.add(input.name);
            frontier.push(input);
        });
    }
    return {
        inputs,
        outputs,
        usedNodes,
        missingInputs,
        dynamicNode,
        syncInputs
    };
}
Given the execution info, return a map from node name to the disposable
node name list after its execution.

@returns A map from node name to disposable nodes after its
    execution. That is, for a node `x`, `nodeLiveUntilMap[x]` indicates
    all nodes which their intermediate tensors should be disposed after `x`
    being executed.