BitstreamRemarkParser.cpp revision 360784
1//===- BitstreamRemarkParser.cpp ------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file provides utility methods used by clients that want to use the
10// parser for remark diagnostics in LLVM.
11//
12//===----------------------------------------------------------------------===//
13
14#include "llvm/Remarks/BitstreamRemarkParser.h"
15#include "BitstreamRemarkParser.h"
16#include "llvm/Remarks/BitstreamRemarkContainer.h"
17#include "llvm/Support/MemoryBuffer.h"
18#include "llvm/Support/Path.h"
19
20using namespace llvm;
21using namespace llvm::remarks;
22
23static Error unknownRecord(const char *BlockName, unsigned RecordID) {
24  return createStringError(
25      std::make_error_code(std::errc::illegal_byte_sequence),
26      "Error while parsing %s: unknown record entry (%lu).", BlockName,
27      RecordID);
28}
29
30static Error malformedRecord(const char *BlockName, const char *RecordName) {
31  return createStringError(
32      std::make_error_code(std::errc::illegal_byte_sequence),
33      "Error while parsing %s: malformed record entry (%s).", BlockName,
34      RecordName);
35}
36
37BitstreamMetaParserHelper::BitstreamMetaParserHelper(
38    BitstreamCursor &Stream, BitstreamBlockInfo &BlockInfo)
39    : Stream(Stream), BlockInfo(BlockInfo) {}
40
41/// Parse a record and fill in the fields in the parser.
42static Error parseRecord(BitstreamMetaParserHelper &Parser, unsigned Code) {
43  BitstreamCursor &Stream = Parser.Stream;
44  // Note: 2 is used here because it's the max number of fields we have per
45  // record.
46  SmallVector<uint64_t, 2> Record;
47  StringRef Blob;
48  Expected<unsigned> RecordID = Stream.readRecord(Code, Record, &Blob);
49  if (!RecordID)
50    return RecordID.takeError();
51
52  switch (*RecordID) {
53  case RECORD_META_CONTAINER_INFO: {
54    if (Record.size() != 2)
55      return malformedRecord("BLOCK_META", "RECORD_META_CONTAINER_INFO");
56    Parser.ContainerVersion = Record[0];
57    Parser.ContainerType = Record[1];
58    break;
59  }
60  case RECORD_META_REMARK_VERSION: {
61    if (Record.size() != 1)
62      return malformedRecord("BLOCK_META", "RECORD_META_REMARK_VERSION");
63    Parser.RemarkVersion = Record[0];
64    break;
65  }
66  case RECORD_META_STRTAB: {
67    if (Record.size() != 0)
68      return malformedRecord("BLOCK_META", "RECORD_META_STRTAB");
69    Parser.StrTabBuf = Blob;
70    break;
71  }
72  case RECORD_META_EXTERNAL_FILE: {
73    if (Record.size() != 0)
74      return malformedRecord("BLOCK_META", "RECORD_META_EXTERNAL_FILE");
75    Parser.ExternalFilePath = Blob;
76    break;
77  }
78  default:
79    return unknownRecord("BLOCK_META", *RecordID);
80  }
81  return Error::success();
82}
83
84BitstreamRemarkParserHelper::BitstreamRemarkParserHelper(
85    BitstreamCursor &Stream)
86    : Stream(Stream) {}
87
88/// Parse a record and fill in the fields in the parser.
89static Error parseRecord(BitstreamRemarkParserHelper &Parser, unsigned Code) {
90  BitstreamCursor &Stream = Parser.Stream;
91  // Note: 5 is used here because it's the max number of fields we have per
92  // record.
93  SmallVector<uint64_t, 5> Record;
94  StringRef Blob;
95  Expected<unsigned> RecordID = Stream.readRecord(Code, Record, &Blob);
96  if (!RecordID)
97    return RecordID.takeError();
98
99  switch (*RecordID) {
100  case RECORD_REMARK_HEADER: {
101    if (Record.size() != 4)
102      return malformedRecord("BLOCK_REMARK", "RECORD_REMARK_HEADER");
103    Parser.Type = Record[0];
104    Parser.RemarkNameIdx = Record[1];
105    Parser.PassNameIdx = Record[2];
106    Parser.FunctionNameIdx = Record[3];
107    break;
108  }
109  case RECORD_REMARK_DEBUG_LOC: {
110    if (Record.size() != 3)
111      return malformedRecord("BLOCK_REMARK", "RECORD_REMARK_DEBUG_LOC");
112    Parser.SourceFileNameIdx = Record[0];
113    Parser.SourceLine = Record[1];
114    Parser.SourceColumn = Record[2];
115    break;
116  }
117  case RECORD_REMARK_HOTNESS: {
118    if (Record.size() != 1)
119      return malformedRecord("BLOCK_REMARK", "RECORD_REMARK_HOTNESS");
120    Parser.Hotness = Record[0];
121    break;
122  }
123  case RECORD_REMARK_ARG_WITH_DEBUGLOC: {
124    if (Record.size() != 5)
125      return malformedRecord("BLOCK_REMARK", "RECORD_REMARK_ARG_WITH_DEBUGLOC");
126    // Create a temporary argument. Use that as a valid memory location for this
127    // argument entry.
128    Parser.TmpArgs.emplace_back();
129    Parser.TmpArgs.back().KeyIdx = Record[0];
130    Parser.TmpArgs.back().ValueIdx = Record[1];
131    Parser.TmpArgs.back().SourceFileNameIdx = Record[2];
132    Parser.TmpArgs.back().SourceLine = Record[3];
133    Parser.TmpArgs.back().SourceColumn = Record[4];
134    Parser.Args =
135        ArrayRef<BitstreamRemarkParserHelper::Argument>(Parser.TmpArgs);
136    break;
137  }
138  case RECORD_REMARK_ARG_WITHOUT_DEBUGLOC: {
139    if (Record.size() != 2)
140      return malformedRecord("BLOCK_REMARK",
141                             "RECORD_REMARK_ARG_WITHOUT_DEBUGLOC");
142    // Create a temporary argument. Use that as a valid memory location for this
143    // argument entry.
144    Parser.TmpArgs.emplace_back();
145    Parser.TmpArgs.back().KeyIdx = Record[0];
146    Parser.TmpArgs.back().ValueIdx = Record[1];
147    Parser.Args =
148        ArrayRef<BitstreamRemarkParserHelper::Argument>(Parser.TmpArgs);
149    break;
150  }
151  default:
152    return unknownRecord("BLOCK_REMARK", *RecordID);
153  }
154  return Error::success();
155}
156
157template <typename T>
158static Error parseBlock(T &ParserHelper, unsigned BlockID,
159                        const char *BlockName) {
160  BitstreamCursor &Stream = ParserHelper.Stream;
161  Expected<BitstreamEntry> Next = Stream.advance();
162  if (!Next)
163    return Next.takeError();
164  if (Next->Kind != BitstreamEntry::SubBlock || Next->ID != BlockID)
165    return createStringError(
166        std::make_error_code(std::errc::illegal_byte_sequence),
167        "Error while parsing %s: expecting [ENTER_SUBBLOCK, %s, ...].",
168        BlockName, BlockName);
169  if (Stream.EnterSubBlock(BlockID))
170    return createStringError(
171        std::make_error_code(std::errc::illegal_byte_sequence),
172        "Error while entering %s.", BlockName);
173
174  // Stop when there is nothing to read anymore or when we encounter an
175  // END_BLOCK.
176  while (!Stream.AtEndOfStream()) {
177    Next = Stream.advance();
178    if (!Next)
179      return Next.takeError();
180    switch (Next->Kind) {
181    case BitstreamEntry::EndBlock:
182      return Error::success();
183    case BitstreamEntry::Error:
184    case BitstreamEntry::SubBlock:
185      return createStringError(
186          std::make_error_code(std::errc::illegal_byte_sequence),
187          "Error while parsing %s: expecting records.", BlockName);
188    case BitstreamEntry::Record:
189      if (Error E = parseRecord(ParserHelper, Next->ID))
190        return E;
191      continue;
192    }
193  }
194  // If we're here, it means we didn't get an END_BLOCK yet, but we're at the
195  // end of the stream. In this case, error.
196  return createStringError(
197      std::make_error_code(std::errc::illegal_byte_sequence),
198      "Error while parsing %s: unterminated block.", BlockName);
199}
200
201Error BitstreamMetaParserHelper::parse() {
202  return parseBlock(*this, META_BLOCK_ID, "META_BLOCK");
203}
204
205Error BitstreamRemarkParserHelper::parse() {
206  return parseBlock(*this, REMARK_BLOCK_ID, "REMARK_BLOCK");
207}
208
209BitstreamParserHelper::BitstreamParserHelper(StringRef Buffer)
210    : Stream(Buffer) {}
211
212Expected<std::array<char, 4>> BitstreamParserHelper::parseMagic() {
213  std::array<char, 4> Result;
214  for (unsigned i = 0; i < 4; ++i)
215    if (Expected<unsigned> R = Stream.Read(8))
216      Result[i] = *R;
217    else
218      return R.takeError();
219  return Result;
220}
221
222Error BitstreamParserHelper::parseBlockInfoBlock() {
223  Expected<BitstreamEntry> Next = Stream.advance();
224  if (!Next)
225    return Next.takeError();
226  if (Next->Kind != BitstreamEntry::SubBlock ||
227      Next->ID != llvm::bitc::BLOCKINFO_BLOCK_ID)
228    return createStringError(
229        std::make_error_code(std::errc::illegal_byte_sequence),
230        "Error while parsing BLOCKINFO_BLOCK: expecting [ENTER_SUBBLOCK, "
231        "BLOCKINFO_BLOCK, ...].");
232
233  Expected<Optional<BitstreamBlockInfo>> MaybeBlockInfo =
234      Stream.ReadBlockInfoBlock();
235  if (!MaybeBlockInfo)
236    return MaybeBlockInfo.takeError();
237
238  if (!*MaybeBlockInfo)
239    return createStringError(
240        std::make_error_code(std::errc::illegal_byte_sequence),
241        "Error while parsing BLOCKINFO_BLOCK.");
242
243  BlockInfo = **MaybeBlockInfo;
244
245  Stream.setBlockInfo(&BlockInfo);
246  return Error::success();
247}
248
249static Expected<bool> isBlock(BitstreamCursor &Stream, unsigned BlockID) {
250  bool Result = false;
251  uint64_t PreviousBitNo = Stream.GetCurrentBitNo();
252  Expected<BitstreamEntry> Next = Stream.advance();
253  if (!Next)
254    return Next.takeError();
255  switch (Next->Kind) {
256  case BitstreamEntry::SubBlock:
257    // Check for the block id.
258    Result = Next->ID == BlockID;
259    break;
260  case BitstreamEntry::Error:
261    return createStringError(
262        std::make_error_code(std::errc::illegal_byte_sequence),
263        "Unexpected error while parsing bitstream.");
264  default:
265    Result = false;
266    break;
267  }
268  if (Error E = Stream.JumpToBit(PreviousBitNo))
269    return std::move(E);
270  return Result;
271}
272
273Expected<bool> BitstreamParserHelper::isMetaBlock() {
274  return isBlock(Stream, META_BLOCK_ID);
275}
276
277Expected<bool> BitstreamParserHelper::isRemarkBlock() {
278  return isBlock(Stream, META_BLOCK_ID);
279}
280
281static Error validateMagicNumber(StringRef MagicNumber) {
282  if (MagicNumber != remarks::ContainerMagic)
283    return createStringError(std::make_error_code(std::errc::invalid_argument),
284                             "Unknown magic number: expecting %s, got %.4s.",
285                             remarks::ContainerMagic.data(), MagicNumber.data());
286  return Error::success();
287}
288
289static Error advanceToMetaBlock(BitstreamParserHelper &Helper) {
290  Expected<std::array<char, 4>> MagicNumber = Helper.parseMagic();
291  if (!MagicNumber)
292    return MagicNumber.takeError();
293  if (Error E = validateMagicNumber(
294          StringRef(MagicNumber->data(), MagicNumber->size())))
295    return E;
296  if (Error E = Helper.parseBlockInfoBlock())
297    return E;
298  Expected<bool> isMetaBlock = Helper.isMetaBlock();
299  if (!isMetaBlock)
300    return isMetaBlock.takeError();
301  if (!*isMetaBlock)
302    return createStringError(
303        std::make_error_code(std::errc::illegal_byte_sequence),
304        "Expecting META_BLOCK after the BLOCKINFO_BLOCK.");
305  return Error::success();
306}
307
308Expected<std::unique_ptr<BitstreamRemarkParser>>
309remarks::createBitstreamParserFromMeta(
310    StringRef Buf, Optional<ParsedStringTable> StrTab,
311    Optional<StringRef> ExternalFilePrependPath) {
312  BitstreamParserHelper Helper(Buf);
313  Expected<std::array<char, 4>> MagicNumber = Helper.parseMagic();
314  if (!MagicNumber)
315    return MagicNumber.takeError();
316
317  if (Error E = validateMagicNumber(
318          StringRef(MagicNumber->data(), MagicNumber->size())))
319    return std::move(E);
320
321  auto Parser =
322      StrTab ? std::make_unique<BitstreamRemarkParser>(Buf, std::move(*StrTab))
323             : std::make_unique<BitstreamRemarkParser>(Buf);
324
325  if (ExternalFilePrependPath)
326    Parser->ExternalFilePrependPath = *ExternalFilePrependPath;
327
328  return std::move(Parser);
329}
330
331Expected<std::unique_ptr<Remark>> BitstreamRemarkParser::next() {
332  if (ParserHelper.atEndOfStream())
333    return make_error<EndOfFileError>();
334
335  if (!ReadyToParseRemarks) {
336    if (Error E = parseMeta())
337      return std::move(E);
338    ReadyToParseRemarks = true;
339  }
340
341  return parseRemark();
342}
343
344Error BitstreamRemarkParser::parseMeta() {
345  // Advance and to the meta block.
346  if (Error E = advanceToMetaBlock(ParserHelper))
347    return E;
348
349  BitstreamMetaParserHelper MetaHelper(ParserHelper.Stream,
350                                       ParserHelper.BlockInfo);
351  if (Error E = MetaHelper.parse())
352    return E;
353
354  if (Error E = processCommonMeta(MetaHelper))
355    return E;
356
357  switch (ContainerType) {
358  case BitstreamRemarkContainerType::Standalone:
359    return processStandaloneMeta(MetaHelper);
360  case BitstreamRemarkContainerType::SeparateRemarksFile:
361    return processSeparateRemarksFileMeta(MetaHelper);
362  case BitstreamRemarkContainerType::SeparateRemarksMeta:
363    return processSeparateRemarksMetaMeta(MetaHelper);
364  }
365  llvm_unreachable("Unknown BitstreamRemarkContainerType enum");
366}
367
368Error BitstreamRemarkParser::processCommonMeta(
369    BitstreamMetaParserHelper &Helper) {
370  if (Optional<uint64_t> Version = Helper.ContainerVersion)
371    ContainerVersion = *Version;
372  else
373    return createStringError(
374        std::make_error_code(std::errc::illegal_byte_sequence),
375        "Error while parsing BLOCK_META: missing container version.");
376
377  if (Optional<uint8_t> Type = Helper.ContainerType) {
378    // Always >= BitstreamRemarkContainerType::First since it's unsigned.
379    if (*Type > static_cast<uint8_t>(BitstreamRemarkContainerType::Last))
380      return createStringError(
381          std::make_error_code(std::errc::illegal_byte_sequence),
382          "Error while parsing BLOCK_META: invalid container type.");
383
384    ContainerType = static_cast<BitstreamRemarkContainerType>(*Type);
385  } else
386    return createStringError(
387        std::make_error_code(std::errc::illegal_byte_sequence),
388        "Error while parsing BLOCK_META: missing container type.");
389
390  return Error::success();
391}
392
393static Error processStrTab(BitstreamRemarkParser &P,
394                           Optional<StringRef> StrTabBuf) {
395  if (!StrTabBuf)
396    return createStringError(
397        std::make_error_code(std::errc::illegal_byte_sequence),
398        "Error while parsing BLOCK_META: missing string table.");
399  // Parse and assign the string table.
400  P.StrTab.emplace(*StrTabBuf);
401  return Error::success();
402}
403
404static Error processRemarkVersion(BitstreamRemarkParser &P,
405                                  Optional<uint64_t> RemarkVersion) {
406  if (!RemarkVersion)
407    return createStringError(
408        std::make_error_code(std::errc::illegal_byte_sequence),
409        "Error while parsing BLOCK_META: missing remark version.");
410  P.RemarkVersion = *RemarkVersion;
411  return Error::success();
412}
413
414Error BitstreamRemarkParser::processExternalFilePath(
415    Optional<StringRef> ExternalFilePath) {
416  if (!ExternalFilePath)
417    return createStringError(
418        std::make_error_code(std::errc::illegal_byte_sequence),
419        "Error while parsing BLOCK_META: missing external file path.");
420
421  SmallString<80> FullPath(ExternalFilePrependPath);
422  sys::path::append(FullPath, *ExternalFilePath);
423
424  // External file: open the external file, parse it, check if its metadata
425  // matches the one from the separate metadata, then replace the current parser
426  // with the one parsing the remarks.
427  ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
428      MemoryBuffer::getFile(FullPath);
429  if (std::error_code EC = BufferOrErr.getError())
430    return createFileError(FullPath, EC);
431
432  TmpRemarkBuffer = std::move(*BufferOrErr);
433
434  // Don't try to parse the file if it's empty.
435  if (TmpRemarkBuffer->getBufferSize() == 0)
436    return make_error<EndOfFileError>();
437
438  // Create a separate parser used for parsing the separate file.
439  ParserHelper = BitstreamParserHelper(TmpRemarkBuffer->getBuffer());
440  // Advance and check until we can parse the meta block.
441  if (Error E = advanceToMetaBlock(ParserHelper))
442    return E;
443  // Parse the meta from the separate file.
444  // Note: here we overwrite the BlockInfo with the one from the file. This will
445  // be used to parse the rest of the file.
446  BitstreamMetaParserHelper SeparateMetaHelper(ParserHelper.Stream,
447                                               ParserHelper.BlockInfo);
448  if (Error E = SeparateMetaHelper.parse())
449    return E;
450
451  uint64_t PreviousContainerVersion = ContainerVersion;
452  if (Error E = processCommonMeta(SeparateMetaHelper))
453    return E;
454
455  if (ContainerType != BitstreamRemarkContainerType::SeparateRemarksFile)
456    return createStringError(
457        std::make_error_code(std::errc::illegal_byte_sequence),
458        "Error while parsing external file's BLOCK_META: wrong container "
459        "type.");
460
461  if (PreviousContainerVersion != ContainerVersion)
462    return createStringError(
463        std::make_error_code(std::errc::illegal_byte_sequence),
464        "Error while parsing external file's BLOCK_META: mismatching versions: "
465        "original meta: %lu, external file meta: %lu.",
466        PreviousContainerVersion, ContainerVersion);
467
468  // Process the meta from the separate file.
469  return processSeparateRemarksFileMeta(SeparateMetaHelper);
470}
471
472Error BitstreamRemarkParser::processStandaloneMeta(
473    BitstreamMetaParserHelper &Helper) {
474  if (Error E = processStrTab(*this, Helper.StrTabBuf))
475    return E;
476  return processRemarkVersion(*this, Helper.RemarkVersion);
477}
478
479Error BitstreamRemarkParser::processSeparateRemarksFileMeta(
480    BitstreamMetaParserHelper &Helper) {
481  return processRemarkVersion(*this, Helper.RemarkVersion);
482}
483
484Error BitstreamRemarkParser::processSeparateRemarksMetaMeta(
485    BitstreamMetaParserHelper &Helper) {
486  if (Error E = processStrTab(*this, Helper.StrTabBuf))
487    return E;
488  return processExternalFilePath(Helper.ExternalFilePath);
489}
490
491Expected<std::unique_ptr<Remark>> BitstreamRemarkParser::parseRemark() {
492  BitstreamRemarkParserHelper RemarkHelper(ParserHelper.Stream);
493  if (Error E = RemarkHelper.parse())
494    return std::move(E);
495
496  return processRemark(RemarkHelper);
497}
498
499Expected<std::unique_ptr<Remark>>
500BitstreamRemarkParser::processRemark(BitstreamRemarkParserHelper &Helper) {
501  std::unique_ptr<Remark> Result = std::make_unique<Remark>();
502  Remark &R = *Result;
503
504  if (StrTab == None)
505    return createStringError(
506        std::make_error_code(std::errc::invalid_argument),
507        "Error while parsing BLOCK_REMARK: missing string table.");
508
509  if (!Helper.Type)
510    return createStringError(
511        std::make_error_code(std::errc::illegal_byte_sequence),
512        "Error while parsing BLOCK_REMARK: missing remark type.");
513
514  // Always >= Type::First since it's unsigned.
515  if (*Helper.Type > static_cast<uint8_t>(Type::Last))
516    return createStringError(
517        std::make_error_code(std::errc::illegal_byte_sequence),
518        "Error while parsing BLOCK_REMARK: unknown remark type.");
519
520  R.RemarkType = static_cast<Type>(*Helper.Type);
521
522  if (!Helper.RemarkNameIdx)
523    return createStringError(
524        std::make_error_code(std::errc::illegal_byte_sequence),
525        "Error while parsing BLOCK_REMARK: missing remark name.");
526
527  if (Expected<StringRef> RemarkName = (*StrTab)[*Helper.RemarkNameIdx])
528    R.RemarkName = *RemarkName;
529  else
530    return RemarkName.takeError();
531
532  if (!Helper.PassNameIdx)
533    return createStringError(
534        std::make_error_code(std::errc::illegal_byte_sequence),
535        "Error while parsing BLOCK_REMARK: missing remark pass.");
536
537  if (Expected<StringRef> PassName = (*StrTab)[*Helper.PassNameIdx])
538    R.PassName = *PassName;
539  else
540    return PassName.takeError();
541
542  if (!Helper.FunctionNameIdx)
543    return createStringError(
544        std::make_error_code(std::errc::illegal_byte_sequence),
545        "Error while parsing BLOCK_REMARK: missing remark function name.");
546  if (Expected<StringRef> FunctionName = (*StrTab)[*Helper.FunctionNameIdx])
547    R.FunctionName = *FunctionName;
548  else
549    return FunctionName.takeError();
550
551  if (Helper.SourceFileNameIdx && Helper.SourceLine && Helper.SourceColumn) {
552    Expected<StringRef> SourceFileName = (*StrTab)[*Helper.SourceFileNameIdx];
553    if (!SourceFileName)
554      return SourceFileName.takeError();
555    R.Loc.emplace();
556    R.Loc->SourceFilePath = *SourceFileName;
557    R.Loc->SourceLine = *Helper.SourceLine;
558    R.Loc->SourceColumn = *Helper.SourceColumn;
559  }
560
561  if (Helper.Hotness)
562    R.Hotness = *Helper.Hotness;
563
564  if (!Helper.Args)
565    return std::move(Result);
566
567  for (const BitstreamRemarkParserHelper::Argument &Arg : *Helper.Args) {
568    if (!Arg.KeyIdx)
569      return createStringError(
570          std::make_error_code(std::errc::illegal_byte_sequence),
571          "Error while parsing BLOCK_REMARK: missing key in remark argument.");
572    if (!Arg.ValueIdx)
573      return createStringError(
574          std::make_error_code(std::errc::illegal_byte_sequence),
575          "Error while parsing BLOCK_REMARK: missing value in remark "
576          "argument.");
577
578    // We have at least a key and a value, create an entry.
579    R.Args.emplace_back();
580
581    if (Expected<StringRef> Key = (*StrTab)[*Arg.KeyIdx])
582      R.Args.back().Key = *Key;
583    else
584      return Key.takeError();
585
586    if (Expected<StringRef> Value = (*StrTab)[*Arg.ValueIdx])
587      R.Args.back().Val = *Value;
588    else
589      return Value.takeError();
590
591    if (Arg.SourceFileNameIdx && Arg.SourceLine && Arg.SourceColumn) {
592      if (Expected<StringRef> SourceFileName =
593              (*StrTab)[*Arg.SourceFileNameIdx]) {
594        R.Args.back().Loc.emplace();
595        R.Args.back().Loc->SourceFilePath = *SourceFileName;
596        R.Args.back().Loc->SourceLine = *Arg.SourceLine;
597        R.Args.back().Loc->SourceColumn = *Arg.SourceColumn;
598      } else
599        return SourceFileName.takeError();
600    }
601  }
602
603  return std::move(Result);
604}
605