//===--  BitcodeWriter.h - ClangDoc Bitcode Writer --------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements a writer for serializing the clang-doc internal
// representation to LLVM bitcode. The writer takes in a stream and emits the
// generated bitcode to that stream.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEWRITER_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEWRITER_H

#include "Representation.h"
#include "clang/AST/AST.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Bitstream/BitstreamWriter.h"
#include <initializer_list>
#include <vector>

namespace clang {
namespace doc {

// Current version number of clang-doc bitcode.
// Should be bumped when removing or changing BlockIds, RecordIds, or
// BitCodeConstants, though they can be added without breaking it.
static const unsigned VersionNumber = 3;

struct BitCodeConstants {
  static constexpr unsigned RecordSize = 32U;
  static constexpr unsigned SignatureBitSize = 8U;
  static constexpr unsigned SubblockIDSize = 4U;
  static constexpr unsigned BoolSize = 1U;
  static constexpr unsigned IntSize = 16U;
  static constexpr unsigned StringLengthSize = 16U;
  static constexpr unsigned FilenameLengthSize = 16U;
  static constexpr unsigned LineNumberSize = 32U;
  static constexpr unsigned ReferenceTypeSize = 8U;
  static constexpr unsigned USRLengthSize = 6U;
  static constexpr unsigned USRBitLengthSize = 8U;
  static constexpr unsigned char Signature[4] = {'D', 'O', 'C', 'S'};
  static constexpr int USRHashSize = 20;
};

// New Ids need to be added to both the enum here and the relevant IdNameMap in
// the implementation file.
enum BlockId {
  BI_VERSION_BLOCK_ID = llvm::bitc::FIRST_APPLICATION_BLOCKID,
  BI_NAMESPACE_BLOCK_ID,
  BI_ENUM_BLOCK_ID,
  BI_ENUM_VALUE_BLOCK_ID,
  BI_TYPE_BLOCK_ID,
  BI_FIELD_TYPE_BLOCK_ID,
  BI_MEMBER_TYPE_BLOCK_ID,
  BI_RECORD_BLOCK_ID,
  BI_BASE_RECORD_BLOCK_ID,
  BI_FUNCTION_BLOCK_ID,
  BI_COMMENT_BLOCK_ID,
  BI_REFERENCE_BLOCK_ID,
  BI_TEMPLATE_BLOCK_ID,
  BI_TEMPLATE_SPECIALIZATION_BLOCK_ID,
  BI_TEMPLATE_PARAM_BLOCK_ID,
  BI_CONSTRAINT_BLOCK_ID,
  BI_TYPEDEF_BLOCK_ID,
  BI_CONCEPT_BLOCK_ID,
  BI_VAR_BLOCK_ID,
  BI_FRIEND_BLOCK_ID,
  BI_LAST,
  BI_FIRST = BI_VERSION_BLOCK_ID
};

// New Ids need to be added to the enum here, and to the relevant IdNameMap and
// initialization list in the implementation file.
enum RecordId {
  VERSION = 1,
  FUNCTION_USR,
  FUNCTION_NAME,
  FUNCTION_DEFLOCATION,
  FUNCTION_LOCATION,
  FUNCTION_ACCESS,
  FUNCTION_IS_METHOD,
  FUNCTION_IS_STATIC,
  COMMENT_KIND,
  COMMENT_TEXT,
  COMMENT_NAME,
  COMMENT_DIRECTION,
  COMMENT_PARAMNAME,
  COMMENT_CLOSENAME,
  COMMENT_SELFCLOSING,
  COMMENT_EXPLICIT,
  COMMENT_ATTRKEY,
  COMMENT_ATTRVAL,
  COMMENT_ARG,
  TYPE_IS_BUILTIN,
  TYPE_IS_TEMPLATE,
  FIELD_TYPE_NAME,
  FIELD_DEFAULT_VALUE,
  FIELD_TYPE_IS_BUILTIN,
  FIELD_TYPE_IS_TEMPLATE,
  MEMBER_TYPE_NAME,
  MEMBER_TYPE_ACCESS,
  MEMBER_TYPE_IS_STATIC,
  MEMBER_TYPE_IS_BUILTIN,
  MEMBER_TYPE_IS_TEMPLATE,
  NAMESPACE_USR,
  NAMESPACE_NAME,
  NAMESPACE_PATH,
  ENUM_USR,
  ENUM_NAME,
  ENUM_DEFLOCATION,
  ENUM_LOCATION,
  ENUM_SCOPED,
  ENUM_VALUE_NAME,
  ENUM_VALUE_VALUE,
  ENUM_VALUE_EXPR,
  RECORD_USR,
  RECORD_NAME,
  RECORD_PATH,
  RECORD_DEFLOCATION,
  RECORD_LOCATION,
  RECORD_TAG_TYPE,
  RECORD_IS_TYPE_DEF,
  RECORD_MANGLED_NAME,
  BASE_RECORD_USR,
  BASE_RECORD_NAME,
  BASE_RECORD_PATH,
  BASE_RECORD_TAG_TYPE,
  BASE_RECORD_IS_VIRTUAL,
  BASE_RECORD_ACCESS,
  BASE_RECORD_IS_PARENT,
  REFERENCE_USR,
  REFERENCE_NAME,
  REFERENCE_QUAL_NAME,
  REFERENCE_TYPE,
  REFERENCE_PATH,
  REFERENCE_FIELD,
  TEMPLATE_PARAM_CONTENTS,
  TEMPLATE_SPECIALIZATION_OF,
  TYPEDEF_USR,
  TYPEDEF_NAME,
  TYPEDEF_DEFLOCATION,
  TYPEDEF_IS_USING,
  CONCEPT_USR,
  CONCEPT_NAME,
  CONCEPT_IS_TYPE,
  CONCEPT_CONSTRAINT_EXPRESSION,
  CONSTRAINT_EXPRESSION,
  VAR_USR,
  VAR_NAME,
  VAR_DEFLOCATION,
  VAR_IS_STATIC,
  FRIEND_IS_CLASS,
  RI_LAST,
  RI_FIRST = VERSION
};

static constexpr unsigned BlockIdCount = BI_LAST - BI_FIRST;
static constexpr unsigned RecordIdCount = RI_LAST - RI_FIRST;

// Identifiers for differentiating between subblocks
enum class FieldId {
  F_default,
  F_namespace,
  F_parent,
  F_vparent,
  F_type,
  F_child_namespace,
  F_child_record,
  F_concept,
  F_friend
};

class ClangDocBitcodeWriter {
public:
  ClangDocBitcodeWriter(llvm::BitstreamWriter &Stream) : Stream(Stream) {
    emitHeader();
    emitBlockInfoBlock();
    emitVersionBlock();
  }

  // Write a specific info to a bitcode stream.
  bool dispatchInfoForWrite(Info *I);

  // Block emission of different info types.
  void emitBlock(const NamespaceInfo &I);
  void emitBlock(const RecordInfo &I);
  void emitBlock(const BaseRecordInfo &I);
  void emitBlock(const FunctionInfo &I);
  void emitBlock(const EnumInfo &I);
  void emitBlock(const EnumValueInfo &I);
  void emitBlock(const TypeInfo &B);
  void emitBlock(const TypedefInfo &B);
  void emitBlock(const FieldTypeInfo &B);
  void emitBlock(const MemberTypeInfo &T);
  void emitBlock(const CommentInfo &B);
  void emitBlock(const TemplateInfo &T);
  void emitBlock(const TemplateSpecializationInfo &T);
  void emitBlock(const TemplateParamInfo &T);
  void emitBlock(const ConceptInfo &T);
  void emitBlock(const ConstraintInfo &T);
  void emitBlock(const Reference &B, FieldId F);
  void emitBlock(const FriendInfo &R);
  void emitBlock(const VarInfo &B);

private:
  class AbbreviationMap {
    llvm::DenseMap<unsigned, unsigned> Abbrevs;

  public:
    AbbreviationMap() : Abbrevs(RecordIdCount) {}

    void add(RecordId RID, unsigned AbbrevID);
    unsigned get(RecordId RID) const;
  };

  class StreamSubBlockGuard {
    llvm::BitstreamWriter &Stream;

  public:
    StreamSubBlockGuard(llvm::BitstreamWriter &Stream_, BlockId ID)
        : Stream(Stream_) {
      // NOTE: SubBlockIDSize could theoretically be calculated on the fly,
      // based on the initialization list of records in each block.
      Stream.EnterSubblock(ID, BitCodeConstants::SubblockIDSize);
    }

    StreamSubBlockGuard(const StreamSubBlockGuard &) = delete;
    StreamSubBlockGuard &operator=(const StreamSubBlockGuard &) = delete;

    ~StreamSubBlockGuard() { Stream.ExitBlock(); }
  };

  // Emission of validation and overview blocks.
  void emitHeader();
  void emitVersionBlock();
  void emitRecordID(RecordId ID);
  void emitBlockID(BlockId ID);
  void emitBlockInfoBlock();
  void emitBlockInfo(BlockId BID, const std::vector<RecordId> &RIDs);

  // Emission of individual record types.
  void emitRecord(StringRef Str, RecordId ID);
  void emitRecord(const SymbolID &Str, RecordId ID);
  void emitRecord(const Location &Loc, RecordId ID);
  void emitRecord(const Reference &Ref, RecordId ID);
  void emitRecord(bool Value, RecordId ID);
  void emitRecord(int Value, RecordId ID);
  void emitRecord(unsigned Value, RecordId ID);
  void emitRecord(const TemplateInfo &Templ);
  bool prepRecordData(RecordId ID, bool ShouldEmit = true);

  // Emission of appropriate abbreviation type.
  void emitAbbrev(RecordId ID, BlockId Block);

  // Static size is the maximum length of the block/record names we're pushing
  // to this + 1. Longest is currently `MemberTypeBlock` at 15 chars.
  SmallVector<uint32_t, BitCodeConstants::RecordSize> Record;
  llvm::BitstreamWriter &Stream;
  AbbreviationMap Abbrevs;
};

} // namespace doc
} // namespace clang

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEWRITER_H
