Home | History | Annotate | Download | only in c
      1 /* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
      2 
      3 Licensed under the Apache License, Version 2.0 (the "License");
      4 you may not use this file except in compliance with the License.
      5 You may obtain a copy of the License at
      6 
      7     http://www.apache.org/licenses/LICENSE-2.0
      8 
      9 Unless required by applicable law or agreed to in writing, software
     10 distributed under the License is distributed on an "AS IS" BASIS,
     11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 See the License for the specific language governing permissions and
     13 limitations under the License.
     14 ==============================================================================*/
     15 
     16 #include "tensorflow/c/c_api_internal.h"
     17 
     18 #include <algorithm>
     19 #include <unordered_map>
     20 #include <unordered_set>
     21 
     22 #include "tensorflow/core/framework/attr_value_util.h"
     23 #include "tensorflow/core/framework/function.pb.h"
     24 #include "tensorflow/core/framework/node_def.pb.h"
     25 #include "tensorflow/core/framework/node_def_util.h"
     26 #include "tensorflow/core/framework/types.h"
     27 #include "tensorflow/core/graph/graph.h"
     28 #include "tensorflow/core/lib/strings/base64.h"
     29 #include "tensorflow/core/lib/strings/strcat.h"
     30 
     31 using tensorflow::errors::InvalidArgument;
     32 
     33 namespace tensorflow {
     34 namespace {
     35 
     36 // Class that maintains a one-to-one original node name -> new node name
     37 // mapping. We normalize the names used as input and output arguments to match
     38 // regexp "[a-z][a-z0-9_]*" specified in definition of ArgDef.name.
     39 // Once we rename them, we risk creating a name collision with the other
     40 // node names, so if necessary we add a suffix to make
     41 // names unique. If we have an input named "A" and a node in the function
     42 // body named "a", they will be renamed to "a" and "a_0".
     43 class NodeNameMapping {
     44  public:
     45   NodeNameMapping() = default;
     46 
     47   // Normalize the input name and make it unique. This is the same as the
     48   // function for output, expect that it adds a name mapping for the name.
     49   string GetInputName(const string& name);
     50 
     51   // Normalize the output name and make it unique.
     52   string GetOutputName(const string& name);
     53 
     54   // Make the node name unique.
     55   string Uniquify(const string& name);
     56 
     57   // Records name as a used name. If this name is already used,
     58   // returns an error status.
     59   Status UseOutputName(const string& name);
     60 
     61   // Look up how a node name was previously normalized/uniquified.
     62   // Returns empty if name was never seen.
     63   string Lookup(const string& name) const;
     64 
     65  private:
     66   string UniquifyHelper(const string& name) const;
     67   static string Normalize(string name);
     68 
     69   // The normalized/uniquified names already used as
     70   // input names (in signature), output names (in signature), and node names
     71   // (in node_def).
     72   // This is a superset of values in name_mapping_.
     73   std::unordered_set<string> used_names_;
     74   // Mapping from original node name from the graph to the normalized
     75   // and uniquified version of it.
     76   std::unordered_map<string, string> name_mapping_;
     77 };
     78 
     79 string NodeNameMapping::Normalize(string name) {
     80   // Convert letters to lowercase and non-alphanumeric characters to '_'.
     81   if (name.empty()) return "unknown";
     82   const int n = name.size();
     83   for (int i = 0; i < n; ++i) {
     84     char c = name[i];
     85     if (isalnum(c)) {
     86       if (isupper(c)) {
     87         name[i] = tolower(c);
     88       }
     89     } else {
     90       name[i] = '_';
     91     }
     92   }
     93 
     94   // Find the first letter and start with it.
     95   int i = 0;
     96   for (; i < n; ++i) {
     97     if (isalpha(name[i])) break;
     98   }
     99 
    100   // Return "unknown" if none of the name's chars were letters.
    101   return i == n ? "unknown" : name.substr(i);
    102 }
    103 
    104 string NodeNameMapping::UniquifyHelper(const string& name) const {
    105   // If the name hasn't been used yet, use it as-is.
    106   if (used_names_.find(name) == used_names_.end()) return name;
    107   // Add a suffix to name to make it unique.
    108   for (int i = 0;; ++i) {
    109     const string candidate = strings::StrCat(name, "_", i);
    110     if (used_names_.find(candidate) == used_names_.end()) return candidate;
    111   }
    112 }
    113 
    114 string NodeNameMapping::GetInputName(const string& name) {
    115   const string& input_name = GetOutputName(name);
    116   name_mapping_[name] = input_name;
    117   return input_name;
    118 }
    119 
    120 string NodeNameMapping::GetOutputName(const string& name) {
    121   const string& input_name = UniquifyHelper(Normalize(name));
    122   // Record that we used this name, but don't add it to name_mapping_
    123   // since this name is not for a node.
    124   used_names_.insert(input_name);
    125   return input_name;
    126 }
    127 
    128 string NodeNameMapping::Uniquify(const string& name) {
    129   const string uniqued = UniquifyHelper(name);
    130   name_mapping_[name] = uniqued;
    131   used_names_.insert(uniqued);
    132   return uniqued;
    133 }
    134 
    135 Status NodeNameMapping::UseOutputName(const string& name) {
    136   const auto& iter = used_names_.find(name);
    137   if (iter != used_names_.end()) {
    138     return InvalidArgument("Cannot have duplicate output names. Name '", name,
    139                            "' appears more than once in 'output_names' array.");
    140   }
    141   used_names_.insert(iter, name);
    142   return Status::OK();
    143 }
    144 
    145 string NodeNameMapping::Lookup(const string& name) const {
    146   const auto iter = name_mapping_.find(name);
    147   if (iter == name_mapping_.end()) return string();
    148   return iter->second;
    149 }
    150 
    151 Status ValidateNonRefOutput(const Node* node, int idx) {
    152   const DataType& dt = node->output_type(idx);
    153   return IsRefType(dt)
    154              ? InvalidArgument("Output ", idx, " of node '", node->name(),
    155                                "' has a reference type ", DataTypeString(dt))
    156              : Status::OK();
    157 }
    158 
    159 Status FillFunctionBody(
    160     const string& fn_name, const NodeNameMapping& node_names,
    161     const std::vector<const Node*>& body_nodes,
    162     const std::unordered_map<string, string>& tensor_renaming,
    163     FunctionDef* fdef) {
    164   std::vector<const Edge*> in_edges;
    165   std::vector<const Edge*> control_edges;
    166   for (const Node* node : body_nodes) {
    167     NodeDef* node_def = fdef->add_node_def();
    168     // First, copy the node_def as is. We will patch it next.
    169     *node_def = node->def();
    170     if (!node->assigned_device_name().empty()) {
    171       node_def->set_device(node->assigned_device_name());
    172     }
    173     node_def->set_name(node_names.Lookup(node->name()));
    174 
    175     // Input names must be set based on nested names in tensor_renaming.
    176     // Clear the flat input names we got from the original node_def
    177     // from the graph.
    178     node_def->clear_input();
    179 
    180     // Collect regular and control inputs. Regular inputs are indexed
    181     // by the index at which they come into the `node`. Control inputs
    182     // don't follow any order.
    183     in_edges.clear();
    184     in_edges.resize(node->num_inputs(), nullptr);
    185     control_edges.clear();
    186     for (const Edge* edge : node->in_edges()) {
    187       if (edge->src()->IsSource()) continue;
    188       if (edge->IsControlEdge()) {
    189         control_edges.push_back(edge);
    190       } else {
    191         in_edges[edge->dst_input()] = edge;
    192       }
    193     }
    194 
    195     // Add regular inputs.
    196     for (size_t i = 0; i < in_edges.size(); ++i) {
    197       const Edge* edge = in_edges[i];
    198       string original_input_name;
    199       if (edge == nullptr) {
    200         // A backedge might not appear as a regular Edge, but be only present
    201         // in the node_def. Such edges are referred to as requested_inputs().
    202         if (i >= node->requested_inputs().size()) {
    203           return InvalidArgument(
    204               "Graph to be converted to function appears to be malformed. ",
    205               "Node ", node->name(), " is missing input edge ", i);
    206         }
    207         original_input_name =
    208             ParseTensorName(node->requested_inputs()[i]).ToString();
    209       } else {
    210         original_input_name =
    211             strings::StrCat(edge->src()->name(), ":", edge->src_output());
    212       }
    213 
    214       const auto iter = tensor_renaming.find(original_input_name);
    215       if (iter == tensor_renaming.end()) {
    216         return InvalidArgument(
    217             "Input ", i, ", '", original_input_name, "', of node '",
    218             node->name(), "' in function '", fn_name,
    219             "' is not available. You might need to include it in inputs "
    220             "or include its source node in the body");
    221       }
    222       node_def->add_input(iter->second);
    223     }
    224 
    225     // Add control inputs.
    226     for (const Edge* edge : control_edges) {
    227       // Add this control input only if the src node is in the body or a part of
    228       // the inputs.
    229       const string normalized = node_names.Lookup(edge->src()->name());
    230       // If we did not find a name for the source of control edge, this
    231       // source must be outside of the body, and not an input. Raise an error.
    232       if (normalized.empty()) {
    233         return InvalidArgument(
    234             "The source of control edge ", edge->DebugString(),
    235             " is not in the body. Encountered while creating function '",
    236             fn_name, "'");
    237       }
    238       node_def->add_input(strings::StrCat("^", normalized));
    239     }
    240 
    241     // A function is stateful if any of its nodes are stateful.
    242     if (node->op_def().is_stateful()) {
    243       fdef->mutable_signature()->set_is_stateful(true);
    244     }
    245   }
    246   return Status::OK();
    247 }
    248 
    249 // Graph to FunctionDef conversion. This code is closely modeled on the Python
    250 // code in tensorflow/python/framework/function.py.
    251 Status GraphToFunctionDef(const Graph& fn_body, const string& fn_name,
    252                           bool append_hash_to_fn_name,
    253                           const std::vector<const Node*>& body_nodes,
    254                           const std::vector<OutputTensor>& inputs,
    255                           const std::vector<OutputTensor>& outputs,
    256                           const std::vector<string>& output_names,
    257                           const char* description, FunctionDef* fdef) {
    258   if (!output_names.empty()) {
    259     DCHECK_EQ(output_names.size(), outputs.size());
    260   }
    261 
    262   if (description != nullptr) {
    263     fdef->mutable_signature()->set_description(description);
    264   }
    265 
    266   // Keep track of names we used and how we normalized them.
    267   NodeNameMapping node_names;
    268 
    269   // Mapping from original names of tensors (i.e. "<node_name>:<idx>") to the
    270   // name we used in the function:
    271   //  - For input tensors:
    272   //    {flat_tensor_name -> normalized_name_of_src_node}
    273   //    e.g. {In:3 -> in}
    274   //  - For tensors produced by nodes in function's body:
    275   //    {flat_tensor_name -> nested_tensor_name}
    276   //    e.g. {Add:3 -> add_0:z:1}
    277   std::unordered_map<string, string> tensor_renaming;
    278 
    279   // Fill outputs in function's signature.
    280   // We fill the outputs first to prevent output_names from colliding
    281   // with the input names we pick below. With this order, no names are used in
    282   // node_names yet, and output_names won't collide with anything (except
    283   // potentially with themselves).
    284   for (size_t i = 0; i < outputs.size(); ++i) {
    285     const Node* node = outputs[i].node;
    286     int idx = outputs[i].index;
    287     OpDef::ArgDef* argdef = fdef->mutable_signature()->add_output_arg();
    288     argdef->set_type(node->output_type(idx));
    289     if (!output_names.empty()) {
    290       TF_RETURN_IF_ERROR(node_names.UseOutputName(output_names[i]));
    291       argdef->set_name(output_names[i]);
    292     } else {
    293       argdef->set_name(node_names.GetOutputName(node->name()));
    294     }
    295   }
    296 
    297   // Fill inputs in function's signature.
    298   for (size_t i = 0; i < inputs.size(); ++i) {
    299     const Node* node = inputs[i].node;
    300     int idx = inputs[i].index;
    301     OpDef::ArgDef* argdef = fdef->mutable_signature()->add_input_arg();
    302     argdef->set_type(node->output_type(idx));
    303     const string& input_name = node_names.GetInputName(node->name());
    304     argdef->set_name(input_name);
    305     tensor_renaming[strings::StrCat(node->name(), ":", idx)] = input_name;
    306   }
    307 
    308   // Populate tensor_renaming and node_names.
    309   // Generate the new output names for every node in the function.
    310   // The NodeDefs in FunctionDefs use a different naming scheme for
    311   // their inputs than the NodeDefs in a graph (see the comment for
    312   // FunctionDef.node_def in function.proto). We do the
    313   // graph tensor name -> function tensor name conversion for every
    314   // possible input (i.e. every node's outputs) and store the result
    315   // in tensor_renaming.
    316   for (const Node* node : body_nodes) {
    317     // Make sure node_name does not collide with an input or output name.
    318     const string& node_name = node_names.Uniquify(node->name());
    319     // For each output_arg in the op_def, the output_ranges
    320     // map will have [start, end] range of indices that this arg produces
    321     // among all the output tensors of this op.
    322     NameRangeMap output_ranges;
    323     TF_RETURN_IF_ERROR(
    324         NameRangesForNode(*node, node->op_def(), nullptr, &output_ranges));
    325     for (const auto& output : output_ranges) {
    326       const StringPiece& output_name = output.first;
    327       int index_start = output.second.first;
    328       int index_end = output.second.second;
    329       for (int i = index_start; i < index_end; ++i) {
    330         const string& original_name = strings::StrCat(node->name(), ":", i);
    331         const string& new_name =
    332             strings::StrCat(node_name, ":", output_name, ":", i - index_start);
    333         // Record the mapping if this tensor is not already mapped.
    334         // Tensor can be already mapped if it is used as an input.
    335         if (tensor_renaming.find(original_name) == tensor_renaming.end()) {
    336           tensor_renaming[original_name] = new_name;
    337         }
    338       }
    339     }
    340   }
    341 
    342   TF_RETURN_IF_ERROR(
    343       FillFunctionBody(fn_name, node_names, body_nodes, tensor_renaming, fdef));
    344 
    345   // Remap return values.
    346   for (int r = 0; r < fdef->signature().output_arg_size(); ++r) {
    347     const string& ret_name = fdef->signature().output_arg(r).name();
    348     // We convert this flat tensor name to the nested value
    349     // (e.g. `add:z:1`) that we stored in tensor_renaming.
    350     const string& return_value =
    351         strings::StrCat(outputs[r].node->name(), ":", outputs[r].index);
    352     const auto iter = tensor_renaming.find(return_value);
    353     if (iter == tensor_renaming.end()) {
    354       return InvalidArgument(
    355           "TF_Output ", return_value, " is neither in the function body ",
    356           "nor among function inputs. Encountered while creating function '",
    357           fn_name, "'");
    358     }
    359     (*fdef->mutable_ret())[ret_name] = iter->second;
    360   }
    361 
    362   if (append_hash_to_fn_name) {
    363     const uint64 hash = FunctionDefHash(*fdef);
    364     string encoded;
    365     TF_RETURN_IF_ERROR(Base64Encode(
    366         StringPiece(reinterpret_cast<const char*>(&hash), sizeof(hash)),
    367         &encoded));
    368     // Besides letters and digits our Base64 encoding uses '_' and '-'.
    369     // Dash is invalid in operation names and multiple underscores in random
    370     // places look strange. Since we never need to decode the hash back,
    371     // replace these chars with with 'a' and 'A'. Replacing with different
    372     // letters keeps more entropy.
    373     std::replace(encoded.begin(), encoded.end(), '-', 'a');
    374     std::replace(encoded.begin(), encoded.end(), '_', 'A');
    375     fdef->mutable_signature()->set_name(strings::StrCat(fn_name, "_", encoded));
    376   } else {
    377     fdef->mutable_signature()->set_name(fn_name);
    378   }
    379 
    380   return Status::OK();
    381 }
    382 
    383 // Converts `ninputs` and `inputs` into `inputs_tensors` and `input_nodes` and
    384 // does various checks while doing so. `input_nodes` will contain the same
    385 // information as input_tensors just in a different structure to make
    386 // following processing easier. TODO(iga): Simplify this nested structure.
    387 Status ProcessInputs(
    388     const TF_Graph* fn_body, const char* fn_name, int ninputs,
    389     const TF_Output* inputs, std::vector<OutputTensor>* input_tensors,
    390     std::unordered_map<const Node*, std::vector<int>>* input_nodes)
    391     EXCLUSIVE_LOCKS_REQUIRED(fn_body->mu) {
    392   input_tensors->reserve(ninputs);
    393   for (int i = 0; i < ninputs; ++i) {
    394     const Node& node = inputs[i].oper->node;
    395     int idx = inputs[i].index;
    396 
    397     TF_RETURN_WITH_CONTEXT_IF_ERROR(
    398         fn_body->graph.IsValidOutputTensor(&node, idx),
    399         "Encountered while processing input ", i, " into function '", fn_name,
    400         "'");
    401     TF_RETURN_WITH_CONTEXT_IF_ERROR(ValidateNonRefOutput(&node, idx),
    402                                     "Encountered while processing input ", i,
    403                                     " into function '", fn_name, "'");
    404 
    405     input_tensors->emplace_back(&node, idx);
    406 
    407     const auto& iter = input_nodes->find(&node);
    408     if (iter == input_nodes->end()) {
    409       input_nodes->insert({&node, {idx}});
    410     } else {
    411       auto& indices = iter->second;
    412       if (std::find(indices.begin(), indices.end(), idx) != indices.end()) {
    413         return InvalidArgument("TF_Output ", node.name(), ":", idx,
    414                                " appears more than once in the input list");
    415       }
    416       indices.push_back(idx);
    417     }
    418   }
    419   return Status::OK();
    420 }
    421 
    422 // Converts `noutputs` and `outputs` into `outputs_tensors` and does various
    423 // checks while doing so.
    424 Status ProcessOutputs(const TF_Graph* fn_body, const char* fn_name,
    425                       int noutputs, const TF_Output* outputs,
    426                       std::vector<OutputTensor>* output_tensors)
    427     EXCLUSIVE_LOCKS_REQUIRED(fn_body->mu) {
    428   output_tensors->reserve(noutputs);
    429   for (int i = 0; i < noutputs; ++i) {
    430     const Node& node = outputs[i].oper->node;
    431     int idx = outputs[i].index;
    432     TF_RETURN_WITH_CONTEXT_IF_ERROR(
    433         fn_body->graph.IsValidOutputTensor(&node, idx),
    434         "Encountered while processing output ", i, " from function '", fn_name,
    435         "'");
    436     TF_RETURN_WITH_CONTEXT_IF_ERROR(ValidateNonRefOutput(&node, idx),
    437                                     "Encountered while creating function '",
    438                                     fn_name, "'");
    439     output_tensors->emplace_back(&node, idx);
    440   }
    441   return Status::OK();
    442 }
    443 
    444 // Populates `body_nodes` with the nodes that will become function's body.
    445 // Performs various checks.
    446 Status ComputeBodyNodes(
    447     const TF_Graph* fn_body, const char* fn_name, int num_opers,
    448     const TF_Operation* const* opers,
    449     const std::unordered_map<const Node*, std::vector<int>>& input_nodes,
    450     std::vector<const Node*>* body_nodes)
    451     EXCLUSIVE_LOCKS_REQUIRED(fn_body->mu) {
    452   if (num_opers == -1) {
    453     for (const Node* node : fn_body->graph.op_nodes()) {
    454       const auto& iter = input_nodes.find(node);
    455       if (iter == input_nodes.end()) {
    456         // This node is not referenced in inputs. Add it to the body.
    457         body_nodes->push_back(node);
    458       } else {
    459         // This node is referenced in inputs. Currently, we place an
    460         // artificial restriction and require that when num_opers=-1, such
    461         // nodes must have a single output.
    462         if (node->num_outputs() != 1) {
    463           return InvalidArgument(
    464               "When `num_opers` is set to -1, nodes referenced in `inputs` "
    465               "must have a single output. Node ",
    466               node->name(), " has ", node->num_outputs(),
    467               " outputs. Encountered while creating function '", fn_name, "'");
    468         }
    469       }
    470     }
    471   } else {
    472     body_nodes->reserve(num_opers);
    473     for (int i = 0; i < num_opers; ++i) {
    474       const Node* node = &opers[i]->node;
    475       body_nodes->push_back(node);
    476     }
    477   }
    478   return Status::OK();
    479 }
    480 
    481 }  // namespace
    482 }  // namespace tensorflow
    483 
    484 using tensorflow::Node;
    485 using tensorflow::string;
    486 
    487 TF_Function* TF_GraphToFunction(const TF_Graph* fn_body, const char* fn_name,
    488                                 unsigned char append_hash_to_fn_name,
    489                                 int num_opers, const TF_Operation* const* opers,
    490                                 int ninputs, const TF_Output* inputs,
    491                                 int noutputs, const TF_Output* outputs,
    492                                 const char* const* output_names,
    493                                 const TF_FunctionOptions* opts,
    494                                 const char* description, TF_Status* status) {
    495   tensorflow::mutex_lock l(*const_cast<tensorflow::mutex*>(&fn_body->mu));
    496 
    497   // Process inputs.
    498   std::vector<tensorflow::OutputTensor> input_tensors;
    499   std::unordered_map<const Node*, std::vector<int>> input_nodes;
    500   status->status = tensorflow::ProcessInputs(fn_body, fn_name, ninputs, inputs,
    501                                              &input_tensors, &input_nodes);
    502   if (!status->status.ok()) return nullptr;
    503 
    504   // Process outputs.
    505   std::vector<tensorflow::OutputTensor> output_tensors;
    506   status->status = tensorflow::ProcessOutputs(fn_body, fn_name, noutputs,
    507                                               outputs, &output_tensors);
    508   if (!status->status.ok()) return nullptr;
    509 
    510   // Process output names.
    511   std::vector<string> output_names_vec;
    512   if (output_names) {
    513     output_names_vec.reserve(noutputs);
    514     for (int i = 0; i < noutputs; ++i) {
    515       output_names_vec.push_back(string(output_names[i]));
    516     }
    517   }
    518 
    519   // Compute body nodes.
    520   std::vector<const Node*> body_nodes;
    521   status->status = tensorflow::ComputeBodyNodes(
    522       fn_body, fn_name, num_opers, opers, input_nodes, &body_nodes);
    523   if (!status->status.ok()) return nullptr;
    524 
    525   // Do the actual function creation.
    526   TF_Function* tf_function = new TF_Function();
    527   DCHECK(append_hash_to_fn_name <= 1);
    528   status->status = tensorflow::GraphToFunctionDef(
    529       fn_body->graph, fn_name, append_hash_to_fn_name != 0, body_nodes,
    530       input_tensors, output_tensors, output_names_vec, description,
    531       &tf_function->fdef);
    532   if (!status->status.ok()) {
    533     TF_DeleteFunction(tf_function);
    534     return nullptr;
    535   }
    536   return tf_function;
    537 }
    538 
    539 void TF_GraphCopyFunction(TF_Graph* g, const TF_Function* func,
    540                           const TF_Function* grad, TF_Status* status) {
    541   if (func == nullptr) {
    542     status->status = InvalidArgument(
    543         "'func' argument to TF_GraphCopyFunction cannot be null");
    544     return;
    545   }
    546 
    547   // TODO(iga): Add AddFunctionDef() and AddGradientDef() methods to graph
    548   // to avoid the extra copy here.
    549   tensorflow::FunctionDefLibrary fdef_lib;
    550   *fdef_lib.add_function() = func->fdef;
    551   if (grad) {
    552     *fdef_lib.add_function() = grad->fdef;
    553     tensorflow::GradientDef* gdef = fdef_lib.add_gradient();
    554     gdef->set_function_name(func->fdef.signature().name());
    555     gdef->set_gradient_func(grad->fdef.signature().name());
    556   }
    557 
    558   tensorflow::mutex_lock l(g->mu);
    559   status->status = g->graph.AddFunctionLibrary(fdef_lib);
    560 }
    561 
    562 int TF_GraphNumFunctions(TF_Graph* g) {
    563   tensorflow::mutex_lock l(g->mu);
    564   return g->graph.flib_def().num_functions();
    565 }
    566 
    567 int TF_GraphGetFunctions(TF_Graph* g, TF_Function** funcs, int max_func,
    568                          TF_Status* status) {
    569   tensorflow::FunctionDefLibrary lib;
    570   {
    571     tensorflow::mutex_lock l(g->mu);
    572     lib = g->graph.flib_def().ToProto();
    573   }
    574   const auto len = std::min(max_func, static_cast<int>(lib.function_size()));
    575   for (int i = 0; i < len; ++i) {
    576     TF_Function* func = new TF_Function();
    577     func->fdef = lib.function(i);
    578     funcs[i] = func;
    579   }
    580   status->status = tensorflow::Status::OK();
    581   return len;
    582 }
    583 
    584 void TF_FunctionToFunctionDef(TF_Function* func, TF_Buffer* output_func_def,
    585                               TF_Status* status) {
    586   status->status = MessageToBuffer(func->fdef, output_func_def);
    587 }
    588 
    589 TF_Function* TF_FunctionImportFunctionDef(const void* proto, size_t proto_len,
    590                                           TF_Status* status) {
    591   TF_Function* func = new TF_Function();
    592   if (!func->fdef.ParseFromArray(proto, proto_len)) {
    593     status->status = InvalidArgument(
    594         "Invalid FunctionDef given to TF_FunctionImportFunctionDef");
    595     TF_DeleteFunction(func);
    596     return nullptr;
    597   }
    598   status->status = tensorflow::Status::OK();
    599   return func;
    600 }
    601 
    602 void TF_FunctionSetAttrValueProto(TF_Function* func, const char* attr_name,
    603                                   const void* proto, size_t proto_len,
    604                                   TF_Status* status) {
    605   tensorflow::AttrValue attr_value;
    606   if (!attr_value.ParseFromArray(proto, proto_len)) {
    607     status->status = InvalidArgument(
    608         "Unparseable AttrValue proto passed to "
    609         "TF_FunctionSetAttrValueProto");
    610     return;
    611   }
    612   (*func->fdef.mutable_attr())[string(attr_name)] = attr_value;
    613   status->status = tensorflow::Status::OK();
    614 }
    615 
    616 void TF_FunctionGetAttrValueProto(TF_Function* func, const char* attr_name,
    617                                   TF_Buffer* output_attr_value,
    618                                   TF_Status* status) {
    619   const auto& it = func->fdef.attr().find(attr_name);
    620   if (it == func->fdef.attr().end()) {
    621     status->status =
    622         InvalidArgument("Function '", func->fdef.signature().name(),
    623                         "' has no attr named '", attr_name, "'.");
    624     return;
    625   }
    626   status->status = MessageToBuffer(it->second, output_attr_value);
    627 }
    628 
    629 void TF_DeleteFunction(TF_Function* func) { delete func; }
    630