Function inlining must preserve function execution semantics with regards to side-effects visibility. Tensorflow in Eager mode has an automatic control dependencies tracking mechanism, which enforces well-defined execution order of all side-effects. Any other frontend (e.g. Swift) must produce graphs following the same rules, to ensure that function inlining works correctly. IMPORTANT: Currently
| 1891 | // |
| 1892 | // TODO(ezhulenev): Documentation above is ahead of implementation below. |
| 1893 | Status InlineFunctionBody(const FunctionLibraryDefinition& flib_def, Graph* g, |
| 1894 | Node* caller, const FunctionBody* fbody, |
| 1895 | const InlineFunctionBodyOptions& options) { |
| 1896 | VLOG(3) << "Inline function call: " << SummarizeNode(*caller) << " [" |
| 1897 | << options.DebugString() << "]"; |
| 1898 | VLOG(5) << "Inlined function definition: " << DebugString(fbody->fdef); |
| 1899 | |
| 1900 | Status validation = ValidateInlining(caller, fbody, options); |
| 1901 | if (!validation.ok()) { |
| 1902 | return errors::Internal("Inlining mismatch: ", validation.error_message()); |
| 1903 | } |
| 1904 | |
| 1905 | // Placer is responsible for assigning devices for all nodes that we will add |
| 1906 | // to the graph. |
| 1907 | const std::unique_ptr<InlinedFunctionBodyPlacer> placer = |
| 1908 | options.inlined_function_body_placer.get(*g, *caller); |
| 1909 | |
| 1910 | // We can't possibly introduce a duplicate control edge during function |
| 1911 | // inlining, so we skip this check in calls to the 'g->AddControlEdge(...)'. |
| 1912 | static constexpr bool kDoNotCheckDuplicates = true; |
| 1913 | |
| 1914 | // ------------------------------------------------------------------------ // |
| 1915 | // Helper functions to create `NoOp` and `Identity` nodes for auxiliary |
| 1916 | // control nodes and inlined function inputs and outputs. |
| 1917 | |
| 1918 | // Add a NoOp node for function control inputs/outputs. |
| 1919 | const auto no_op = [&](StringPiece name) -> Node* { |
| 1920 | Node* node = AddNoOp(absl::StrCat(caller->name(), "/", name), g); |
| 1921 | const absl::optional<string> device = placer->ControlNodeDevice(); |
| 1922 | if (device.has_value()) node->set_requested_device(*device); |
| 1923 | return node; |
| 1924 | }; |
| 1925 | |
| 1926 | // Add an Identity node for function input. |
| 1927 | const auto input_identity = [&](StringPiece name, Endpoint input, |
| 1928 | int index) -> Node* { |
| 1929 | Node* node = AddIdentity(absl::StrCat(caller->name(), "/", name), g, input); |
| 1930 | const absl::optional<string> device = placer->InputNodeDevice(index); |
| 1931 | if (device.has_value()) node->set_requested_device(*device); |
| 1932 | return node; |
| 1933 | }; |
| 1934 | |
| 1935 | // Add an Identity node for function output. |
| 1936 | const auto output_identity = [&](StringPiece name, Endpoint input, |
| 1937 | int index) -> Node* { |
| 1938 | Node* node = AddIdentity(absl::StrCat(caller->name(), "/", name), g, input); |
| 1939 | const absl::optional<string> device = placer->OutputNodeDevice(index); |
| 1940 | if (device.has_value()) node->set_requested_device(*device); |
| 1941 | return node; |
| 1942 | }; |
| 1943 | |
| 1944 | // ------------------------------------------------------------------------ // |
| 1945 | // Input edges. For data edges coming into "caller", we first compute the |
| 1946 | // <src>:<src_output> for the i-th input in "inputs". |
| 1947 | // If "caller" has any input control dependencies, we add a NoOp |
| 1948 | // node "input_control_node", which depends on "caller"'s control inputs. |
| 1949 | std::vector<Endpoint> inputs(caller->num_inputs()); |
| 1950 | Node* input_control_node = nullptr; |
no test coverage detected