Home | History | Annotate | Download | only in service
      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/compiler/xla/service/tuple_points_to_analysis.h"
     17 
     18 #include <ostream>
     19 #include <utility>
     20 #include <vector>
     21 
     22 #include "tensorflow/compiler/xla/map_util.h"
     23 #include "tensorflow/compiler/xla/service/hlo_instruction.h"
     24 #include "tensorflow/compiler/xla/shape_util.h"
     25 #include "tensorflow/compiler/xla/types.h"
     26 #include "tensorflow/compiler/xla/util.h"
     27 #include "tensorflow/core/lib/core/errors.h"
     28 #include "tensorflow/core/lib/strings/str_util.h"
     29 #include "tensorflow/core/lib/strings/strcat.h"
     30 #include "tensorflow/core/lib/strings/stringprintf.h"
     31 #include "tensorflow/core/platform/logging.h"
     32 
     33 namespace xla {
     34 
     35 string BufferAlias::ToString() const {
     36   return tensorflow::strings::StrCat("BufferAlias(", instruction_->name(), "[",
     37                                      tensorflow::str_util::Join(index_, ","),
     38                                      "])");
     39 }
     40 
     41 std::ostream& operator<<(std::ostream& out, const BufferAlias& buffer_alias) {
     42   out << buffer_alias.ToString();
     43   return out;
     44 }
     45 
     46 bool PointsToSet::IsAmbiguous() const {
     47   bool ambiguous = false;
     48   ForEachElement(
     49       [&ambiguous](const ShapeIndex& /*index*/, const BufferList& points_to) {
     50         ambiguous |= points_to.size() > 1;
     51       });
     52   return ambiguous;
     53 }
     54 
     55 bool PointsToSet::IsDistinct() const {
     56   bool distinct = true;
     57   std::set<const LogicalBuffer*> all_points_to;
     58   ForEachElement([&distinct, &all_points_to](const ShapeIndex& /*index*/,
     59                                              const BufferList& points_to) {
     60     for (auto& buffer : points_to) {
     61       if (all_points_to.count(buffer) != 0) {
     62         distinct = false;
     63       }
     64       all_points_to.insert(buffer);
     65     }
     66   });
     67   return distinct;
     68 }
     69 
     70 size_t PointsToSet::size() const {
     71   // Because pointed-to elements may be duplicated we have to create a flattened
     72   // set and return the size.
     73   return CreateFlattenedSet().size();
     74 }
     75 
     76 PointsToSet::BufferSet PointsToSet::CreateFlattenedSet() const {
     77   BufferSet flat_set;
     78   ForEachElement(
     79       [&flat_set](const ShapeIndex& /*index*/, const BufferList& buffers) {
     80         flat_set.insert(buffers.begin(), buffers.end());
     81       });
     82   return flat_set;
     83 }
     84 
     85 bool PointsToSet::ContainsBuffer(const LogicalBuffer& buffer) const {
     86   bool found = false;
     87   ForEachElement([&found, &buffer](const ShapeIndex& /*index*/,
     88                                    const BufferList& pointed_to_buffers) {
     89     if (!found &&
     90         std::find(pointed_to_buffers.begin(), pointed_to_buffers.end(),
     91                   &buffer) != pointed_to_buffers.end()) {
     92       found = true;
     93     }
     94   });
     95   return found;
     96 }
     97 
     98 bool PointsToSet::ContainsBufferAtIndex(const LogicalBuffer& buffer,
     99                                         const ShapeIndex& index) const {
    100   const auto& pointed_to_buffers = element(index);
    101   return std::find(pointed_to_buffers.begin(), pointed_to_buffers.end(),
    102                    &buffer) != pointed_to_buffers.end();
    103 }
    104 
    105 void PointsToSet::AddPointedToBuffer(const LogicalBuffer& buffer,
    106                                      const ShapeIndex& index) {
    107   if (ContainsBufferAtIndex(buffer, index)) {
    108     return;
    109   }
    110   mutable_element(index)->push_back(&buffer);
    111 }
    112 
    113 const PointsToSet::SourceSet& PointsToSet::tuple_sources(
    114     const ShapeIndex& index) const {
    115   return tree_.element(index).tuple_sources;
    116 }
    117 
    118 void PointsToSet::add_tuple_source(const ShapeIndex& index,
    119                                    HloInstruction* tuple) {
    120   tree_.mutable_element(index)->tuple_sources.insert(tuple);
    121 }
    122 
    123 namespace {
    124 
    125 // Gather fusion instructions from 'instruction' into 'fusion_instructions'.
    126 void GatherFusionInstructions(
    127     HloInstruction* instruction,
    128     std::vector<HloInstruction*>* fusion_instructions) {
    129   CHECK_EQ(HloOpcode::kFusion, instruction->opcode());
    130   for (auto* fused : instruction->fused_instructions()) {
    131     if (fused->opcode() == HloOpcode::kFusion) {
    132       GatherFusionInstructions(fused, fusion_instructions);
    133     }
    134   }
    135   fusion_instructions->push_back(instruction);
    136 }
    137 
    138 }  // namespace
    139 
    140 /* static */ StatusOr<std::unique_ptr<TuplePointsToAnalysis>>
    141 TuplePointsToAnalysis::Run(const HloModule* module) {
    142   auto logical_buffer_analysis = LogicalBufferAnalysis::Run(module);
    143   std::unique_ptr<TuplePointsToAnalysis> analysis(new TuplePointsToAnalysis(
    144       module, logical_buffer_analysis.ConsumeValueOrDie()));
    145   TF_RETURN_IF_ERROR(analysis->Analyze());
    146   return std::move(analysis);
    147 }
    148 
    149 Status TuplePointsToAnalysis::Analyze() {
    150   per_instruction_.clear();
    151   per_instruction_.resize(module_->NumUniqueInstructionIds());
    152 
    153   logical_buffer_aliases_.clear();
    154   logical_buffer_aliases_.resize(
    155       logical_buffer_analysis_->num_logical_buffers());
    156 
    157   std::vector<HloInstruction*> fusion_instructions;
    158   for (auto* computation : module_->MakeNonfusionComputations()) {
    159     TF_RETURN_IF_ERROR(computation->Accept(this));
    160     TF_RETURN_IF_ERROR(
    161         PopulateDefinedBuffersAndAliases(computation->instructions()));
    162     for (auto* instruction : computation->instructions()) {
    163       if (instruction->opcode() == HloOpcode::kFusion) {
    164         GatherFusionInstructions(instruction, &fusion_instructions);
    165       }
    166     }
    167   }
    168   // Run points-to analysis on fusion instructions in 'computation'.
    169   for (auto* instruction : fusion_instructions) {
    170     TF_RETURN_IF_ERROR(instruction->fused_expression_root()->Accept(this));
    171     TF_RETURN_IF_ERROR(
    172         PopulateDefinedBuffersAndAliases(instruction->fused_instructions()));
    173   }
    174 
    175   XLA_VLOG_LINES(3, ToString());
    176 
    177   return Status::OK();
    178 }
    179 
    180 Status TuplePointsToAnalysis::PopulateDefinedBuffersAndAliases(const decltype(
    181     std::declval<HloComputation>().instructions())& instructions) {
    182   for (auto* instruction : instructions) {
    183     PerInstruction* pi = PerInst(instruction);
    184     TF_RETURN_IF_ERROR(GatherBuffersDefinedByInstruction(
    185         instruction, &pi->instruction_defined_buffers));
    186 
    187     const PointsToSet& points_to_set = GetPointsToSet(instruction);
    188     points_to_set.ForEachElement(
    189         [this, &instruction](
    190             const ShapeIndex& index,
    191             const PointsToSet::BufferList& pointed_to_buffers) {
    192           for (const LogicalBuffer* buffer : pointed_to_buffers) {
    193             logical_buffer_aliases_[buffer->id()].emplace_back(instruction,
    194                                                                index);
    195           }
    196         });
    197   }
    198   return Status::OK();
    199 }
    200 
    201 Status TuplePointsToAnalysis::DefaultAction(HloInstruction* hlo_instruction) {
    202   // Create trivial points-to set for instruction. Each points-to set at index i
    203   // contains a single element LogicalBuffer(hlo_instruction, i). This indicates
    204   // that this instruction is the source of all buffers in its own output.
    205   PointsToSet& points_to_set = CreateEmptyPointsToSet(hlo_instruction);
    206   points_to_set.ForEachMutableElement(
    207       [this, hlo_instruction](const ShapeIndex& index,
    208                               PointsToSet::BufferList* buffers) {
    209         buffers->push_back(
    210             &logical_buffer_analysis_->GetBuffer(hlo_instruction, index));
    211       });
    212 
    213   if (ShapeUtil::IsTuple(hlo_instruction->shape())) {
    214     // If the hlo instruction is a tuple-shaped, then trivially the instruction
    215     // itself is the source of the tuple.
    216     points_to_set.add_tuple_source({}, hlo_instruction);
    217   }
    218 
    219   return Status::OK();
    220 }
    221 
    222 Status TuplePointsToAnalysis::HandleGetTupleElement(
    223     HloInstruction* get_tuple_element) {
    224   // GetTupleElement forwards a pointer to a particular element of the tuple
    225   // operand.
    226   int64 element_index = get_tuple_element->tuple_index();
    227 
    228   PointsToSet& points_to_set = CreateEmptyPointsToSet(get_tuple_element);
    229   const PointsToSet& operand_points_to_set =
    230       *PerInst(get_tuple_element->operand(0))->points_to_set;
    231 
    232   // Copy the points-to set (and tuple sources) at index {element_index} of the
    233   // operand to the points-to set for this GetTupleElement instruction.
    234   points_to_set.ForEachMutableElement(
    235       [&, this](const ShapeIndex& target_index,
    236                 PointsToSet::BufferList* points_to) {
    237         // Construct an index into the operand by prepending element_index to
    238         // the index for the GetTupleElement instruction's points-to set.
    239         ShapeIndex src_index;
    240         src_index.push_back(element_index);
    241         for (auto element : target_index) {
    242           src_index.push_back(element);
    243         }
    244 
    245         *points_to = operand_points_to_set.element(src_index);
    246         for (HloInstruction* tuple :
    247              operand_points_to_set.tuple_sources(src_index)) {
    248           points_to_set.add_tuple_source(target_index, tuple);
    249         }
    250       });
    251 
    252   return Status::OK();
    253 }
    254 
    255 Status TuplePointsToAnalysis::HandleCopy(HloInstruction* copy) {
    256   // A kCopy instruction performs a shallow copy of the operand. The top-level
    257   // buffer (index={}) is newly created, but all other buffers (in the case of a
    258   // tuple shape) come from the operand
    259   PointsToSet& points_to_set = CreateCopiedPointsToSet(copy, copy->operand(0));
    260   points_to_set.mutable_element(/*index=*/{})->clear();
    261   points_to_set.AddPointedToBuffer(
    262       logical_buffer_analysis_->GetBuffer(copy, /*index=*/{}),
    263       /*index=*/{});
    264 
    265   return Status::OK();
    266 }
    267 
    268 Status TuplePointsToAnalysis::HandleBitcast(HloInstruction* bitcast) {
    269   // A kBitcast instruction aliases its operand. That is, the buffer of its
    270   // result *is* the buffer of its operand, so just copy the operands points-to
    271   // set.
    272   CreateCopiedPointsToSet(bitcast, bitcast->operand(0));
    273   return Status::OK();
    274 }
    275 
    276 Status TuplePointsToAnalysis::HandleSlice(HloInstruction* slice) {
    277   // A kSlice instruction aliases its operand if the backend lowers it to an
    278   // in-place implementation.
    279   if (slice->IsInPlaceSlice()) {
    280     CreateCopiedPointsToSet(slice, slice->operand(0));
    281     return Status::OK();
    282   }
    283   return DefaultAction(slice);
    284 }
    285 
    286 Status TuplePointsToAnalysis::HandleRecvDone(HloInstruction* recv_done) {
    287   // RecvDone aliases its input (Recv) tuple element {0} to its output.
    288   PointsToSet& points_to_set = CreateEmptyPointsToSet(recv_done);
    289   const PointsToSet& operand_points_to_set =
    290       GetPointsToSet(recv_done->operand(0));
    291 
    292   // Recursively copy the points to set of the operand tuple {0}.
    293   points_to_set.ForEachMutableElement(
    294       [this, &points_to_set, &operand_points_to_set](
    295           const ShapeIndex& index, PointsToSet::BufferList* buffers) {
    296         ShapeIndex src_index({0});
    297         for (auto element : index) {
    298           src_index.push_back(element);
    299         }
    300         *buffers = operand_points_to_set.element(src_index);
    301         for (auto& tuple_source :
    302              operand_points_to_set.tuple_sources(src_index)) {
    303           points_to_set.add_tuple_source(index, tuple_source);
    304         }
    305       });
    306   return Status::OK();
    307 }
    308 
    309 Status TuplePointsToAnalysis::HandleSend(HloInstruction* send) {
    310   // Send creates a tuple of {aliased operand, U32 context}.
    311   PointsToSet& points_to_set = CreateEmptyPointsToSet(send);
    312 
    313   // Creates the points to set for the tuple and its element at {1}.
    314   auto top_buffer = points_to_set.mutable_element(ShapeIndex({}));
    315   top_buffer->push_back(
    316       &logical_buffer_analysis_->GetBuffer(send, ShapeIndex({})));
    317   points_to_set.add_tuple_source({}, send);
    318 
    319   auto context_buffer = points_to_set.mutable_element(ShapeIndex({1}));
    320   context_buffer->push_back(
    321       &logical_buffer_analysis_->GetBuffer(send, ShapeIndex({1})));
    322 
    323   // Recursively copy the points to set of the operand to output tuple {0}.
    324   const PointsToSet& operand_points_to_set = GetPointsToSet(send->operand(0));
    325   operand_points_to_set.ForEachElement(
    326       [&points_to_set, &operand_points_to_set](
    327           const ShapeIndex& src_index,
    328           const PointsToSet::BufferList& points_to) {
    329         ShapeIndex target_index({0});
    330         for (auto element : src_index) {
    331           target_index.push_back(element);
    332         }
    333         *points_to_set.mutable_element(target_index) = points_to;
    334 
    335         for (HloInstruction* tuple :
    336              operand_points_to_set.tuple_sources(src_index)) {
    337           points_to_set.add_tuple_source(target_index, tuple);
    338         }
    339       });
    340 
    341   return Status::OK();
    342 }
    343 
    344 Status TuplePointsToAnalysis::HandleTuple(HloInstruction* tuple) {
    345   tensorflow::gtl::ArraySlice<HloInstruction*> operands(tuple->operands());
    346   PointsToSet& points_to_set = CreateEmptyPointsToSet(tuple);
    347   points_to_set.AddPointedToBuffer(
    348       logical_buffer_analysis_->GetBuffer(tuple, /*index=*/{}),
    349       /*index=*/{});
    350 
    351   // A tuple contains references to all input operands and transitively any
    352   // references in those operands.
    353   for (int64 i = 0; i < operands.size(); ++i) {
    354     const PointsToSet& operand_points_to_set =
    355         *PerInst(operands[i])->points_to_set;
    356 
    357     // Copy the points-to set (and tuple sources) of the operand into the
    358     // respective subtree of the tuple instructions points-to set.
    359     operand_points_to_set.ForEachElement(
    360         [&points_to_set, &operand_points_to_set, i](
    361             const ShapeIndex& src_index,
    362             const PointsToSet::BufferList& points_to) {
    363           ShapeIndex target_index;
    364           target_index.push_back(i);
    365           for (auto element : src_index) {
    366             target_index.push_back(element);
    367           }
    368 
    369           *points_to_set.mutable_element(target_index) = points_to;
    370 
    371           for (HloInstruction* tuple :
    372                operand_points_to_set.tuple_sources(src_index)) {
    373             points_to_set.add_tuple_source(target_index, tuple);
    374           }
    375         });
    376   }
    377 
    378   points_to_set.add_tuple_source({}, tuple);
    379 
    380   return Status::OK();
    381 }
    382 
    383 Status TuplePointsToAnalysis::HandleSelect(HloInstruction* select) {
    384   // Select allocates a new buffer and then shallow copies the on_true or
    385   // on_false buffer into this new buffer. Which side is chosen cannot be
    386   // determined statically so conservatively set the points-to set to the union
    387   // of these on_true and on_false operands.
    388   //
    389   // First create a copy of the on_true points-to set (and tuple sources), then
    390   // add in elements of the on_false points-to set (tuple sources).
    391   auto on_true = select->operand(1);
    392   auto on_false = select->operand(2);
    393   PointsToSet& points_to_set = CreateCopiedPointsToSet(select, on_true);
    394   const PointsToSet& false_points_to_set = *PerInst(on_false)->points_to_set;
    395   points_to_set.ForEachMutableElement(
    396       [&](const ShapeIndex& index, PointsToSet::BufferList* buffers) {
    397         for (const LogicalBuffer* false_buffer :
    398              false_points_to_set.element(index)) {
    399           points_to_set.AddPointedToBuffer(*false_buffer, index);
    400         }
    401 
    402         for (HloInstruction* tuple : false_points_to_set.tuple_sources(index)) {
    403           points_to_set.add_tuple_source(index, tuple);
    404         }
    405       });
    406 
    407   // Select creates a new (top-level) buffer to store its result, so its
    408   // respective element in the points-to set should contain only itself.
    409   points_to_set.mutable_element({})->clear();
    410   points_to_set.AddPointedToBuffer(
    411       logical_buffer_analysis_->GetBuffer(select, /*index=*/{}),
    412       /*index=*/{});
    413   return Status::OK();
    414 }
    415 
    416 const PointsToSet& TuplePointsToAnalysis::GetPointsToSet(
    417     const HloInstruction* hlo_instruction) const {
    418   return *PerInst(hlo_instruction)->points_to_set;
    419 }
    420 
    421 PointsToSet& TuplePointsToAnalysis::CreateEmptyPointsToSet(
    422     const HloInstruction* instruction) {
    423   PerInstruction* pi = PerInst(instruction);
    424   CHECK(pi->points_to_set == nullptr)
    425       << "instruction should not have been present in the map.";
    426   auto set = MakeUnique<PointsToSet>(&instruction->shape());
    427   pi->points_to_set = std::move(set);
    428   // Return *set using the iterator returned by emplace.
    429   return *pi->points_to_set;
    430 }
    431 
    432 bool TuplePointsToAnalysis::InstructionDefinesBufferAtIndex(
    433     const HloInstruction* instruction, const ShapeIndex& index) const {
    434   const auto& buffers = GetPointsToSet(instruction).element(index);
    435   return (buffers.size() == 1 && buffers[0]->instruction() == instruction);
    436 }
    437 
    438 Status TuplePointsToAnalysis::VerifyBuffer(const LogicalBuffer& buffer) const {
    439   if (!InstructionDefinesBufferAtIndex(buffer.instruction(), buffer.index())) {
    440     // kSlice ops that are lowered to an in-place version are expected to not
    441     // define their output buffer.
    442     if (buffer.instruction()->opcode() != HloOpcode::kSlice ||
    443         !buffer.instruction()->IsInPlaceSlice()) {
    444       return FailedPrecondition(
    445           "LogicalBuffer %s is ill-defined: instruction %s does not define a "
    446           "buffer at that index",
    447           buffer.ToString().c_str(), buffer.instruction()->name().c_str());
    448     }
    449   }
    450 
    451   if (buffer.id() < 0 ||
    452       buffer.id() >= logical_buffer_analysis_->num_logical_buffers()) {
    453     return FailedPrecondition(
    454         "LogicalBuffer %s is ill-defined: invalid id %lld",
    455         buffer.ToString().c_str(), buffer.id());
    456   }
    457   if (GetBuffer(buffer.id()).instruction() != buffer.instruction() ||
    458       GetBuffer(buffer.id()).index() != buffer.index()) {
    459     return FailedPrecondition(
    460         "LogicalBuffer %s is ill-defined: buffer with same id differs: %s",
    461         buffer.ToString().c_str(), GetBuffer(buffer.id()).ToString().c_str());
    462   }
    463 
    464   return Status::OK();
    465 }
    466 
    467 const LogicalBuffer& TuplePointsToAnalysis::GetBuffer(
    468     LogicalBuffer::Id id) const {
    469   CHECK_GE(id, 0);
    470   CHECK_LT(id, logical_buffer_analysis_->num_logical_buffers());
    471   return logical_buffer_analysis_->GetBuffer(id);
    472 }
    473 
    474 StatusOr<const LogicalBuffer*> TuplePointsToAnalysis::GetBufferDefinedAt(
    475     const HloInstruction* instruction, const ShapeIndex& index) const {
    476   const auto& buffers = GetPointsToSet(instruction).element(index);
    477   if (buffers.size() != 1 || buffers[0]->instruction() != instruction) {
    478     return FailedPrecondition(
    479         "instruction %s does not define buffer at index {%s}",
    480         instruction->name().c_str(),
    481         tensorflow::str_util::Join(index, ",").c_str());
    482   }
    483   return buffers[0];
    484 }
    485 
    486 const TuplePointsToAnalysis::BufferAliasVector&
    487 TuplePointsToAnalysis::GetBufferAliases(const LogicalBuffer& buffer) const {
    488   return logical_buffer_aliases_.at(buffer.id());
    489 }
    490 
    491 const TuplePointsToAnalysis::BufferDefinitionVector&
    492 TuplePointsToAnalysis::GetBuffersDefinedByInstruction(
    493     const HloInstruction* instruction) const {
    494   return PerInst(instruction)->instruction_defined_buffers;
    495 }
    496 
    497 Status TuplePointsToAnalysis::GatherBuffersDefinedByInstruction(
    498     const HloInstruction* instruction,
    499     TuplePointsToAnalysis::BufferDefinitionVector* buffers) {
    500   GetPointsToSet(instruction)
    501       .ForEachElement([this, buffers, instruction](
    502                           const ShapeIndex& index,
    503                           const PointsToSet::BufferList& source_buffers) {
    504         // Add buffers which 'instruction' is the source of.
    505         CHECK(!source_buffers.empty());
    506         if (source_buffers.size() == 1 &&
    507             source_buffers[0]->instruction() == instruction) {
    508           // If this instruction is the source of this buffer the
    509           // indices must match.
    510           DCHECK(source_buffers[0]->index() == index);
    511           buffers->push_back(source_buffers[0]);
    512         } else {
    513           // If the points-to set includes more than one buffer then
    514           // necessarily this instruction did not produce the
    515           // buffer.
    516           for (const LogicalBuffer* source_buffer : source_buffers) {
    517             DCHECK(source_buffer->instruction() != instruction);
    518           }
    519         }
    520       });
    521   return Status::OK();
    522 }
    523 
    524 PointsToSet& TuplePointsToAnalysis::CreateCopiedPointsToSet(
    525     const HloInstruction* instruction, const HloInstruction* src) {
    526   // PointsToSet doesn't have a copy constructor so copy over element-by-element
    527   // from src PointsToSet.
    528   PointsToSet& dst_points_to_set = CreateEmptyPointsToSet(instruction);
    529   const PointsToSet& src_points_to_set = GetPointsToSet(src);
    530   dst_points_to_set.ForEachMutableElement(
    531       [this, &dst_points_to_set, &src_points_to_set](
    532           const ShapeIndex& index, PointsToSet::BufferList* buffers) {
    533         *buffers = src_points_to_set.element(index);
    534         for (auto& tuple_source : src_points_to_set.tuple_sources(index)) {
    535           dst_points_to_set.add_tuple_source(index, tuple_source);
    536         }
    537       });
    538   return *PerInst(instruction)->points_to_set;
    539 }
    540 
    541 string TuplePointsToAnalysis::ToString() const {
    542   string output = tensorflow::strings::Printf(
    543       "TuplePointsToSet for module %s:\n", module_->name().c_str());
    544   for (const auto* computation : module_->MakeNonfusionComputations()) {
    545     const char* entry =
    546         computation == module_->entry_computation() ? "entry " : "";
    547     tensorflow::strings::StrAppend(&output, entry, "computation ",
    548                                    computation->name(), ":\n");
    549     for (const HloInstruction* instruction :
    550          computation->MakeInstructionPostOrder()) {
    551       InstructionToString(instruction, &output);
    552       if (instruction->opcode() == HloOpcode::kFusion) {
    553         for (auto* fused : instruction->fused_instructions()) {
    554           InstructionToString(fused, &output);
    555         }
    556       }
    557     }
    558   }
    559 
    560   tensorflow::strings::StrAppend(&output, "LogicalBuffers:\n");
    561   for (const auto& b : logical_buffer_analysis_->logical_buffers()) {
    562     tensorflow::strings::StrAppend(&output, "  buffer ", b->ToString(), ":\n");
    563     for (const BufferAlias& alias : logical_buffer_aliases_.at(b->id())) {
    564       tensorflow::strings::StrAppend(&output, "    alias ", alias.ToString(),
    565                                      "\n");
    566     }
    567   }
    568   return output;
    569 }
    570 
    571 void TuplePointsToAnalysis::InstructionToString(
    572     const HloInstruction* instruction, string* output) const {
    573   const string prefix = instruction->IsFused() ? "    " : "";
    574   tensorflow::strings::StrAppend(output, prefix, "  instruction ",
    575                                  instruction->ToShortString(), ":\n");
    576   const PointsToSet& points_to_set = GetPointsToSet(instruction);
    577   points_to_set.ForEachElement([&prefix, &output](
    578                                    const ShapeIndex& index,
    579                                    const PointsToSet::BufferList& points_to) {
    580     tensorflow::strings::StrAppend(
    581         output, prefix, "    {", tensorflow::str_util::Join(index, ","), "}: ",
    582         tensorflow::str_util::Join(
    583             points_to, ", ",
    584             [](string* out, const LogicalBuffer* source) {
    585               out->append(source->ToString());
    586             }),
    587         "\n");
    588   });
    589 }
    590 
    591 }  // namespace xla
    592