code: def update_chain(graph, loc, du, ud):
      
      ins = graph.get_ins_from_loc(loc)
      for var in ins.get_used_vars():
          # We get the definition points of the current variable
          for def_loc in set(ud[var, loc]):
              # We remove the use of the variable at loc from the DU chain of
              # the variable definition located at def_loc
              du[var, def_loc].remove(loc)
              ud[var, loc].remove(def_loc)
              if not ud.get((var, loc)):
                  ud.pop((var, loc))
              # If the DU chain of the defined variable is now empty, this means
              # that we may have created a new dead instruction, so we check that
              # the instruction has no side effect and we update the DU chain of
              # the new dead instruction, and we delete it.
              # We also make sure that def_loc is not < 0. This is the case when
              # the current variable is a method parameter.
              if def_loc >= 0 and not du[var, def_loc]:
                  du.pop((var, def_loc))
                  def_ins = graph.get_ins_from_loc(def_loc)
                  if def_ins.is_call():
                      def_ins.remove_defined_var()
                  elif def_ins.has_side_effect():
                      continue
                  else:
                      update_chain(graph, def_loc, du, ud)
                      graph.remove_ins(def_loc)
docs: Updates the DU chain of the instruction located at loc such that there is
      no more reference to it so that we can remove it.
      When an instruction is found to be dead (i.e it has no side effect, and the
      register defined is not used) we have to update the DU chain of all the
      variables that may me used by the dead instruction.