class_docstr: Perform the body repeatedly while the condition ``cond`` is true.
  
  Parameters
  ----------
  _cond: function  (Required)
      * A Python function that takes ``loop_vars`` as positional arguments.
      * The function must return a ``bool`` ``Var``.
  
  _body: function  (Required)
      * A Python function that takes ``loop_vars`` as positional arguments.
      * The function must return the same number of output vars as ``loop_vars``
        with the same types.
  
  loop_vars: tuple (Required)
      * Python tuple of ``Variables``.
  
  _existing_blocks: list[Block] (Optional)
      * Python list of ``Block``.
      * For internal use only. When converting a milproto, we already got existing blocks,
        and the ``build_nested_blocks`` function can use them directly.
      * When ``_existing_blocks`` is set, ``_cond`` and ``_body`` must be dummy functions which returns ``None``.
  
  Returns
  -------
  tuple
      * Python tuple (same type as ``loop_vars``).
class_docstr_tok: ["Perform","the","body","repeatedly","while","the","condition","``","cond","``","is","true",".","Parameters","--","--","--","--","--","_cond",":","function","(","Required",")","*","A","Python","function","that","takes","``","loop_vars","``","as","positional","arguments",".","*","The","function","must","return","a","``","bool","``","``","Var","``",".","_body",":","function","(","Required",")","*","A","Python","function","that","takes","``","loop_vars","``","as","positional","arguments",".","*","The","function","must","return","the","same","number","of","output","vars","as","``","loop_vars","``","with","the","same","types",".","loop_vars",":","tuple","(","Required",")","*","Python","tuple","of","``","Variables","``",".","_existing_blocks",":","list","[","Block","]","(","Optional",")","*","Python","list","of","``","Block","``",".","*","For","internal","use","only",".","When","converting","a","milproto",",","we","already","got","existing","blocks",",","and","the","``","build_nested_blocks","``","function","can","use","them","directly",".","*","When","``","_existing_blocks","``","is","set",",","``","_cond","``","and","``","_body","``","must","be","dummy","functions","which","returns","``","None","``",".","Returns","--","--","--","-","tuple","*","Python","tuple","(","same","type","as","``","loop_vars","``",")","."]
code: def build_nested_blocks(self):
      # self.loop_vars is python tuple of Vars.
  
      # block_inputs Var are not produced by any op.
      # We assume block_inputs have the same types as self.loop_var. If not
      # (e.g., when certain dimensions change shape during iterate), we'd
      # adjust later.
  
      # We assume that sym_val is unchanging across the block iterate. If it
      # changes, we rebuild the block and rerun type and value inference.
  
      # Design notes on two blocks (cond and body):
      #
      # - Observe that two blocks can always be represented as a single
      # block that contains both cond and body logic, which would return
      # [loop_cond] + loop_carries. `loop_cond` is a bool.
      #
      # - Observe that single block implies a do-while logic,
      # in which the first iterate is always executed. It's possible to add
      # a cond input to while_loop to modify do-while behavior:
      #
      #   %first_cond = cond_logic(...)
      #   while_loop(cond=%first_cond, loop_vars=(...))
      #
      # and we enter the first iterate only if cond is True. But this would
      # require caller to execute cond logic outside of while_loop first
      # (which also needs to be duplicated within the loop),
      # resulting in duplicated code / ops.
      #
      # - Thus, single block is unnatural for the natural execution order,
      # in which we execute the cond block first to get the loop_cond. Only
      # if `loop_cond` is True do we execute the body block. This is the
      # semantics of tf.while_loop.
  
      # If the front end is milproto, we already have the well constructed cond/body block.
      # For this case, we set self.blocks directly.
      # We also check that _cond and _body are both dummy functions (return None).
      if (
          self._existing_blocks is not None
          and self._existing_blocks.val is not None
      ):
          assert self._cond.val([]) is None
          assert self._body.val([]) is None
          self.blocks = self._existing_blocks.val
          return
  
      block_inputs = tuple(copy.copy(v) for v in self.loop_vars)
      name_count = {v.name: 0 for v in block_inputs}
      for v in block_inputs:
          v._op = None
          v.op_output_idx = None
          v._child_ops = list()
  
          # Get unique name
  
          old_v_name = v.name
          v.name = v.name + "_x" + str(name_count[v.name])
          name_count[old_v_name] += 1
  
          v._sym_val = v._sym_val
          v.consuming_blocks = list()
  
      cond_block, body_block, exit_vars = self._build_block(
          block_inputs
      )
  
      # Verify exit_vars has the same types as loop_vars
      block_input_type_change = False
      for i, (v_in, v_out) in enumerate(zip(block_inputs, exit_vars)):
          if not is_subtype(v_out.sym_type, v_in.sym_type):
              compat_shape = while_loop.get_compat_shape(
                  v_out.sym_type, v_in.sym_type
              )
              if compat_shape is None:
                  msg = (
                      "loop_vars '{}' changes in the body of "
                      "while_loop '{}':\n {} -> {}"
                  )
                  raise ValueError(
                      msg.format(
                          v_in.name,
                          self.name,
                          v_in.sym_type,
                          v_out.sym_type,
                      )
                  )
              else:
                  block_inputs[i]._sym_type = types.tensor(
                      v_in.dtype, compat_shape
                  )
                  block_input_type_change = True
          if not while_loop._check_equal_value(
              v_out.sym_val, v_in.sym_val
          ):
              block_inputs[i]._sym_val = None
              block_input_type_change = True
  
      if block_input_type_change:
          # Since we are going to build the block again, we first need to remove ops
          # in the block from vars's _child_ops.
          while_loop._clean_up_child_ops(cond_block)
          while_loop._clean_up_child_ops(body_block)
  
          # Rebuild our block to invoke type inference.
          cond_block, body_block, exit_vars = self._build_block(
              block_inputs
          )
          for i, (v_in, v_out) in enumerate(
              zip(block_inputs, exit_vars)
          ):
              if not is_subtype(v_out.sym_type, v_in.sym_type):
                  msg = (
                      "Block output {}: {} is not a subtype of "
                      + "block input {}: {} after factoring shape changes"
                  )
                  raise ValueError(
                      msg.format(
                          v_out.name,
                          v_out.sym_type.__name__,
                          v_in.name,
                          v_in.sym_type.__name__,
                      )
                  )
              if not while_loop._check_equal_value(
                  v_out.sym_val, v_in.sym_val
              ):
                  msg = (
                      "Block output {}: {} is not equal to "
                      + "block input {}: {} after value changes"
                  )
                  raise ValueError(
                      msg.format(
                          v_out.name, v.sym_val, v_in.name, v_in.sym_val
                      )
                  )
      self.blocks.append(cond_block)
      self.blocks.append(body_block)
  
imports: import copy
  import numpy
  from coremltools import _logger
  from coremltools.converters.mil.mil import Block, get_existing_symbol, get_new_symbol, types
  from coremltools.converters.mil.mil.input_type import DefaultInputs, InputSpec, InternalInputType, ListInputType, PyFunctionInputType, TensorInputType, TupleInputType
  from coremltools.converters.mil.mil.operation import NONE, SYMBOL, VALUE, Operation, mil_list, precondition
  from coremltools.converters.mil.mil.ops.defs._op_reqs import register_op
  from coremltools.converters.mil.mil.ops.defs._utils import infer_type_with_broadcast, promoted_primitive_type
  from coremltools.converters.mil.mil.types import is_compatible_type
  from coremltools.converters.mil.mil.types.type_list import list
  from coremltools.converters.mil.mil.types.type_mapping import builtin_to_string, is_subtype, numpy_type_to_builtin_type, numpy_val_to_builtin_val
label_desc: Use image node_id 4 for calling the while_loop obj's underlying member method code with example usage: obj.build_nested_blocks() without return types