TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
  const OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
  Subgraph* this_subgraph = reinterpret_cast<Subgraph*>(context->impl_);
  auto* subgraphs = this_subgraph->GetSubgraphs();
  Subgraph* cond_subgraph = (*subgraphs)[op_data->cond_subgraph_index].get();
  Subgraph* body_subgraph = (*subgraphs)[op_data->body_subgraph_index].get();

  // The follow graph illustrates the current implementation.
  //
  // This Subgraph          Cond Subgraph         Body Subgraph
  // +-----------+   (1)   +------------+   (3)   +------------+
  // |   WHILE   |-------->|  SUBGRAPH  |-------->|  SUBGRAPH  |
  // |   INPUT   |        /|   INPUT    |<-----   |   INPUT    |
  // +-----------+       / +------------+      \  +------------+
  //                    /        |              \       |
  //               (6) /         | (2)       (5) \      | (4)
  //                  /          v                \     v
  // +-----------+   /     +------------+         +------------+
  // |   WHILE   |<--      |  SUBGRAPH  |         |  SUBGRAPH  |
  // |   OUTPUT  |         |   OUTPUT   |         |   OUTPUT   |
  // +-----------+         +------------+         +------------+
  //
  // (1) Copy the inputs of WHILE op to the inputs of condition subgraph.
  // (2) Invoke condition subgraph.
  //     Jump to step 5 if result is false.
  // (3) Copy the inputs of condition subgraph to the inputs of body subgraph.
  // (4) Invoke body subgraph.
  // (5) Copy the outputs of body subgraph to the inputs condition subgraph.
  //     Jump back to step 2!
  // (6) Copy the inputs of condition subgraph to the outputs of WHILE op.
  //
  // If the body subgraph has dynamic sized outputs, it's required to resize the
  // tensor before copying in step 1, 3, 4 and 6.
  //
  // Note the flow is carefully designed to handle the dynamic sized output
  // case. The loop invariant is: The newest value is in the inputs of condition
  // subgraph. This is always true before step 2.
  //
  // This is the best we can do without sharing tensor buffer across subgraph
  // boundary. Currently we copy the input / output between the subgraphs. This
  // isn't optimized yet and a lot of redundant copies are made.
  // TODO(b/120234921): Optimize and avoid copying tensors between subgraphs.

  if (op_data->body_has_dynamic_output_tensors) {
    // If body subgraph has dynamic outputs, the input of condition subgraph may
    // be changed in the last invocation and may need resizing.
    TF_LITE_ENSURE_OK(
        context, CopyTensorsShapeAndType(
                     context, this_subgraph, TfLiteIntArrayView(node->inputs),
                     cond_subgraph, cond_subgraph->inputs(), true));
    TF_LITE_ENSURE_OK(context, cond_subgraph->AllocateTensors());
  }
  TF_LITE_ENSURE_OK(
      context,
      CopyTensorsData(context, this_subgraph, TfLiteIntArrayView(node->inputs),
                      cond_subgraph, cond_subgraph->inputs()));

  while (true) {
    TF_LITE_ENSURE_OK(context, cond_subgraph->Invoke());
    int cond_subgraph_output_index = cond_subgraph->outputs()[0];
    cond_subgraph->EnsureTensorDataIsReadable(cond_subgraph_output_index);
    TfLiteTensor* cond_output =
        cond_subgraph->tensor(cond_subgraph_output_index);
    if (op_data->cond_has_dynamic_output_tensors) {
      TF_LITE_ENSURE_STATUS(CheckCondOutput(context, cond_output));
    }

    if (!cond_output->data.b[0]) {
      break;
    }
    if (op_data->body_has_dynamic_output_tensors) {
      TF_LITE_ENSURE_OK(context,
                        CopyTensorsShapeAndType(
                            context, cond_subgraph, cond_subgraph->inputs(),
                            body_subgraph, body_subgraph->inputs(), true));
      TF_LITE_ENSURE_OK(context, body_subgraph->AllocateTensors());
    }

    TF_LITE_ENSURE_OK(
        context,
        CopyTensorsData(context, cond_subgraph, cond_subgraph->inputs(),
                        body_subgraph, body_subgraph->inputs()));

    TF_LITE_ENSURE_OK(context, body_subgraph->Invoke());

    for (int tensor_index : body_subgraph->outputs()) {
      body_subgraph->EnsureTensorDataIsReadable(tensor_index);
    }

    if (op_data->body_has_dynamic_output_tensors) {
      TF_LITE_ENSURE_OK(context,
                        CopyTensorsShapeAndType(
                            context, body_subgraph, body_subgraph->outputs(),
                            cond_subgraph, cond_subgraph->inputs(), true));
      TF_LITE_ENSURE_OK(context, cond_subgraph->AllocateTensors());
    }

    TF_LITE_ENSURE_OK(
        context,
        CopyTensorsData(context, body_subgraph, body_subgraph->outputs(),
                        cond_subgraph, cond_subgraph->inputs()));
  }

  // Note that copying from body's output will fail if body is never invoked.
  // TODO(b/120234921): Optimize and avoid copying tensors between subgraphs.
  if (op_data->body_has_dynamic_output_tensors) {
    TF_LITE_ENSURE_OK(
        context, CopyTensorsShapeAndType(
                     context, cond_subgraph, cond_subgraph->inputs(),
                     this_subgraph, TfLiteIntArrayView(node->outputs), false));
  }

  TF_LITE_ENSURE_OK(
      context,
      CopyTensorsData(context, cond_subgraph, cond_subgraph->inputs(),
                      this_subgraph, TfLiteIntArrayView(node->outputs)));
  return kTfLiteOk;
}