class_docstr: Track side effects (list mutation, setattr, etc) that need to be
  applied after an FX graph is run.
class_docstr_tok: ["Track","side","effects","(","list","mutation",",","setattr",",","etc",")","that","need","to","be","applied","after","an","FX","graph","is","run","."]
code: def codegen_hooks(self, cg):
      for (
          tensor,
          hook,
          handle,
          name,
      ) in self.tensor_hooks.values():
          # Note: [On tensor.register_hook]
          #
          # register_hook on a tensor, AKA backward hooks, have slightly nuanced differences in how they are implemented
          # when it comes to hooks on objects with sources (inputs, params) vs objects without sources (intermediaries).
          #
          # For tensors with a source, we bypass direct inclusion of register_hook calls in the graph.
          # Instead, these are tracked and stashed as a global variable, enabling their association with tensors in
          # the residuals. During dynamo's frame creation, these hooks are invoked seamlessly on known reconstructible/fetch-able
          # tensors. Because a source indicates knowledge of this object outside the torch compile region, and
          # because we are running residuals firmly before .backward() can be run, it is sound to invoke
          # `register_hook` on a known tensor.
          #
          # For tensors without a source, we support a limited subset of hooks. Global functions only, and
          # compiled_autograd must be enabled or we will graph break.
          #
          # Handling the Handle: When a user retains the register_hook result in a handle, we intercept the
          # STORE_FAST operation to record the user-designated local variable name. This ensures the reconstructed
          # bytecode retains this name. If no handle is defined, we simply pop the generated value to keep the
          # stack intact.
          #
          # Dynamo Tensor Hooks Workflow:
          # - Functions passed to register_hook are lifted globally.
          # - For tensors with sources:
          #   - In the "side_effects" phase of codegen, we iterate over tensors with hooks to:
          #     - Generate the tensor.
          #     - Issue a register_hook call on the tensor, linking to the globally stored function.
          #     - Incorporate a handle if one was established in the eager phase.
          #  - For tensors without sources:
          #    - We don't generate any instructions for registering a hook.
          #    - Handles from intermediary hooks are NYI.
          #    - We produce a call function that utilizes the trace_wrapped higher order op, closing over it.
          #    - We then manually insert the call function above into the graph.
          # - The handle's exact user-specified name, "user_code_variable_name", is discerned and associated during STORE_FAST.
          assert (
              tensor.source
          ), "Hooks on non input tensors NYI - should not get here"
          cg(tensor)
          cg.extend_output([cg.create_load_attr(name)])
          cg(hook)
          cg.extend_output(create_call_function(1, True))
          # Let's go over how handles work.
          #
          # A handle is created from invoking `register_hook` on a tensor. A handle can be referenced at any
          # time after that, or never. In dynamo, we track and associate a name with a handle (user_code_variable_name) to
          # determine if a handle is accessed. If a handle has no user_code_variable_name, we just pop the produced value
          # off the top of the stack, discarding the handle.
          #
          # If a handle is seen, we store it under that name. This is extremely important, because, the handle
          # can be generated at any time after this point, and can be generated multiple times! If we were to defer
          # actual codegen of the handle object until we saw a codegen call to it - then we would end up generating multiple
          # register_hook calls, which is incorrect. This turns the codegen reconstruct(handle) call for the handle into
          # essentially a lookup.
          if (
              hasattr(handle, "user_code_variable_name")
              and handle.user_code_variable_name
          ):
              # register_hook stored with variable name assigned to the handle
              cg.extend_output(
                  [cg.create_store(handle.user_code_variable_name)]
              )
          else:
              # register_hook stored w/o a variable name assigned to the handle
              cg.extend_output([create_instruction("POP_TOP")])
  
imports: import inspect
  from typing import Any, Dict, List, Optional, Union
  import torch.nn
  from .None import utils, variables
  from .bytecode_transformation import create_call_function, create_call_method, create_instruction
  from .codegen import PyCodegen
  from .exc import unimplemented
  from .source import LocalSource, Source
  from .utils import nn_module_new, object_new
  from .variables.base import is_side_effect_safe, MutableLocalBase, MutableLocalSource, VariableTracker
label_desc: Use image node_id 32 for calling the SideEffects obj's underlying member method code with example usage: obj.codegen_hooks(cg) without return types