// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_VM_CODE_DESCRIPTORS_H_
#define RUNTIME_VM_CODE_DESCRIPTORS_H_

#include "vm/datastream.h"
#include "vm/globals.h"
#include "vm/growable_array.h"
#include "vm/log.h"
#include "vm/runtime_entry.h"
#include "vm/token_position.h"

namespace dart {

static constexpr intptr_t kInvalidTryIndex = -1;

class DescriptorList : public ZoneObject {
 public:
  explicit DescriptorList(
      Zone* zone,
      const GrowableArray<const Function*>* inline_id_to_function = nullptr);

  ~DescriptorList() {}

  void AddDescriptor(UntaggedPcDescriptors::Kind kind,
                     intptr_t pc_offset,
                     intptr_t deopt_id,
                     TokenPosition token_pos,
                     intptr_t try_index,
                     intptr_t yield_index);

  PcDescriptorsPtr FinalizePcDescriptors(uword entry_point);

 private:
  static constexpr intptr_t kInitialStreamSize = 64;

  const Function& function_;
  const Script& script_;
  ZoneWriteStream encoded_data_;

  intptr_t prev_pc_offset;
  intptr_t prev_deopt_id;
  int32_t prev_token_pos;

  DISALLOW_COPY_AND_ASSIGN(DescriptorList);
};

class CompressedStackMapsBuilder : public ZoneObject {
 public:
  explicit CompressedStackMapsBuilder(Zone* zone)
      : encoded_bytes_(zone, kInitialStreamSize) {}

  void AddEntry(intptr_t pc_offset,
                BitmapBuilder* bitmap,
                intptr_t spill_slot_bit_count);

  CompressedStackMapsPtr Finalize() const;

 private:
  static constexpr intptr_t kInitialStreamSize = 16;

  ZoneWriteStream encoded_bytes_;
  intptr_t last_pc_offset_ = 0;
  DISALLOW_COPY_AND_ASSIGN(CompressedStackMapsBuilder);
};

class ExceptionHandlerList : public ZoneObject {
 public:
  struct HandlerDesc {
    intptr_t outer_try_index;    // Try block in which this try block is nested.
    intptr_t pc_offset;          // Handler PC offset value.
    bool is_generated;           // False if this is directly from Dart code.
    const Array* handler_types;  // Catch clause guards.
    bool needs_stacktrace;
  };

  explicit ExceptionHandlerList(const Function& function)
      : list_(),
        has_async_handler_(
            (function.IsAsyncFunction() || function.IsAsyncGenerator()) &&
            !function.is_declared_in_bytecode()) {}

  intptr_t Length() const { return list_.length(); }

  void AddPlaceHolder() {
    struct HandlerDesc data;
    data.outer_try_index = -1;
    data.pc_offset = ExceptionHandlers::kInvalidPcOffset;
    data.is_generated = true;
    data.handler_types = nullptr;
    data.needs_stacktrace = false;
    list_.Add(data);
  }

  void AddHandler(intptr_t try_index,
                  intptr_t outer_try_index,
                  intptr_t pc_offset,
                  bool is_generated,
                  const Array& handler_types,
                  bool needs_stacktrace) {
    ASSERT(try_index >= 0);
    while (Length() <= try_index) {
      AddPlaceHolder();
    }
    list_[try_index].outer_try_index = outer_try_index;
    ASSERT(list_[try_index].pc_offset == ExceptionHandlers::kInvalidPcOffset);
    list_[try_index].pc_offset = pc_offset;
    list_[try_index].is_generated = is_generated;
    DEBUG_ASSERT(handler_types.IsNotTemporaryScopedHandle());
    list_[try_index].handler_types = &handler_types;
    list_[try_index].needs_stacktrace |= needs_stacktrace;
  }

  // Called by rethrows, to mark their enclosing handlers.
  void SetNeedsStackTrace(intptr_t try_index) {
    // Rethrows can be generated outside a try by the compiler.
    if (try_index == kInvalidTryIndex) {
      return;
    }
    ASSERT(try_index >= 0);
    while (Length() <= try_index) {
      AddPlaceHolder();
    }
    list_[try_index].needs_stacktrace = true;
  }

  static bool ContainsCatchAllType(const Array& array) {
    auto& type = AbstractType::Handle();
    for (intptr_t i = 0; i < array.Length(); i++) {
      type ^= array.At(i);
      if (type.IsCatchAllType()) {
        return true;
      }
    }
    return false;
  }

  ExceptionHandlersPtr FinalizeExceptionHandlers(uword entry_point) const;

 private:
  GrowableArray<struct HandlerDesc> list_;
  const bool has_async_handler_;
  DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerList);
};

#if !defined(DART_PRECOMPILED_RUNTIME)
// Used to construct CatchEntryMoves for the AOT mode of compilation.
class CatchEntryMovesMapBuilder : public ZoneObject {
 public:
  CatchEntryMovesMapBuilder();

  void NewMapping(intptr_t pc_offset);
  void Append(const CatchEntryMove& move);
  void EndMapping();
  TypedDataPtr FinalizeCatchEntryMovesMap();

 private:
  class TrieNode;

  Zone* zone_;
  TrieNode* root_;
  intptr_t current_pc_offset_;
  GrowableArray<CatchEntryMove> moves_;
  ZoneWriteStream stream_;

  DISALLOW_COPY_AND_ASSIGN(CatchEntryMovesMapBuilder);
};
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

// Instructions have two pieces of information needed to get accurate source
// locations: the token position and the inlining id. The inlining id tells us
// which function, and thus which script, to use for this instruction and the
// token position, when real, tells us the position in the source for the
// script for the instruction.
//
// Thus, we bundle the two pieces of information in InstructionSource structs
// when copying or retrieving to lower the likelihood that the token position
// is used without the appropriate inlining id.
struct InstructionSource {
  // Treat an instruction source without inlining id information as unset.
  InstructionSource() : InstructionSource(TokenPosition::kNoSource) {}
  explicit InstructionSource(TokenPosition pos) : InstructionSource(pos, -1) {}
  InstructionSource(TokenPosition pos, intptr_t inlining_id)
      : token_pos(pos), inlining_id(inlining_id) {}

  const TokenPosition token_pos;
  const intptr_t inlining_id;

  DISALLOW_ALLOCATION();
};

struct CodeSourceMapOps : AllStatic {
  static constexpr uint8_t kChangePosition = 0;
  static constexpr uint8_t kAdvancePC = 1;
  static constexpr uint8_t kPushFunction = 2;
  static constexpr uint8_t kPopFunction = 3;
  static constexpr uint8_t kNullCheck = 4;

  static uint8_t Read(ReadStream* stream,
                      int32_t* arg1,
                      int32_t* arg2 = nullptr);

  static void Write(BaseWriteStream* stream,
                    uint8_t op,
                    int32_t arg1 = 0,
                    int32_t arg2 = 0);

 private:
  static constexpr intptr_t kOpBits = 3;

  using OpField = BitField<int32_t, uint8_t, 0, kOpBits>;
  using ArgField = BitField<int32_t, int32_t, OpField::kNextBit>;

  static constexpr int32_t kMaxArgValue =
      Utils::NBitMask<int32_t>(ArgField::bitsize() - 1);
  static constexpr int32_t kMinArgValue = ~kMaxArgValue;
  static constexpr int32_t kSignBits = static_cast<uint32_t>(kMinArgValue) << 1;
};

// A CodeSourceMap maps from pc offsets to a stack of inlined functions and
// their positions. This is encoded as a little bytecode that pushes and pops
// functions and changes the top function's position as the PC advances.
// Decoding happens by running this bytecode until we reach the desired PC.
//
// The implementation keeps track of two sets of state: one written to the byte
// stream and one that is buffered. On the JIT, this buffering effectively gives
// us a peephole optimization that merges adjacent advance PC bytecodes. On AOT,
// this allows to skip encoding our position until we reach a PC where we might
// throw.
class CodeSourceMapBuilder : public ZoneObject {
 public:
  CodeSourceMapBuilder(
      Zone* zone,
      bool stack_traces_only,
      const GrowableArray<intptr_t>& caller_inline_id,
      const GrowableArray<TokenPosition>& inline_id_to_token_pos,
      const GrowableArray<const Function*>& inline_id_to_function);

  // The position at which a function implicitly starts, for both the root and
  // after a push bytecode. We use the classifying position kDartCodePrologue
  // since it is the most common.
  static const TokenPosition& kInitialPosition;

  void BeginCodeSourceRange(int32_t pc_offset, const InstructionSource& source);
  void EndCodeSourceRange(int32_t pc_offset, const InstructionSource& source);
  void NoteDescriptor(UntaggedPcDescriptors::Kind kind,
                      int32_t pc_offset,
                      const InstructionSource& source);
  void NoteNullCheck(int32_t pc_offset,
                     const InstructionSource& source,
                     intptr_t name_index);
  void WriteFunctionEntrySourcePosition(const InstructionSource& source);

  // If source is from an inlined call, returns the token position of the
  // original call in the root function, otherwise the source's token position.
  TokenPosition RootPosition(const InstructionSource& source);
  ArrayPtr InliningIdToFunction();
  CodeSourceMapPtr Finalize();

  const GrowableArray<const Function*>& inline_id_to_function() const {
    return inline_id_to_function_;
  }

 private:
  intptr_t GetFunctionId(intptr_t inline_id);
  void StartInliningInterval(int32_t pc_offset,
                             const InstructionSource& source);

  void BufferChangePosition(TokenPosition pos);
  void WriteChangePosition(TokenPosition pos);
  void BufferAdvancePC(int32_t distance) { buffered_pc_offset_ += distance; }
  void WriteAdvancePC(int32_t distance) {
    CodeSourceMapOps::Write(&stream_, CodeSourceMapOps::kAdvancePC, distance);
    written_pc_offset_ += distance;
  }
  void BufferPush(intptr_t inline_id) {
    buffered_inline_id_stack_.Add(inline_id);
    buffered_token_pos_stack_.Add(kInitialPosition);
  }
  void WritePush(intptr_t inline_id) {
    CodeSourceMapOps::Write(&stream_, CodeSourceMapOps::kPushFunction,
                            GetFunctionId(inline_id));
    written_inline_id_stack_.Add(inline_id);
    written_token_pos_stack_.Add(kInitialPosition);
  }
  void BufferPop() {
    buffered_inline_id_stack_.RemoveLast();
    buffered_token_pos_stack_.RemoveLast();
  }
  void WritePop() {
    CodeSourceMapOps::Write(&stream_, CodeSourceMapOps::kPopFunction);
    written_inline_id_stack_.RemoveLast();
    written_token_pos_stack_.RemoveLast();
  }
  void WriteNullCheck(int32_t name_index) {
    CodeSourceMapOps::Write(&stream_, CodeSourceMapOps::kNullCheck, name_index);
  }

  void FlushBuffer();

  bool IsOnBufferedStack(intptr_t inline_id) {
    for (intptr_t i = 0; i < buffered_inline_id_stack_.length(); i++) {
      if (buffered_inline_id_stack_[i] == inline_id) return true;
    }
    return false;
  }

  Zone* const zone_;
  intptr_t buffered_pc_offset_;
  GrowableArray<intptr_t> buffered_inline_id_stack_;
  GrowableArray<TokenPosition> buffered_token_pos_stack_;

  intptr_t written_pc_offset_;
  GrowableArray<intptr_t> written_inline_id_stack_;
  GrowableArray<TokenPosition> written_token_pos_stack_;

  const GrowableArray<intptr_t>& caller_inline_id_;
  const GrowableArray<TokenPosition>& inline_id_to_token_pos_;
  const GrowableArray<const Function*>& inline_id_to_function_;

  const GrowableObjectArray& inlined_functions_;

  Script& script_;
  ZoneWriteStream stream_;

  const bool stack_traces_only_;

  DISALLOW_COPY_AND_ASSIGN(CodeSourceMapBuilder);
};

class CodeSourceMapReader : public ValueObject {
 public:
  CodeSourceMapReader(const CodeSourceMap& map,
                      const Array& functions,
                      const Function& root)
      : map_(map), functions_(functions), root_(root) {}

  void GetInlinedFunctionsAt(int32_t pc_offset,
                             GrowableArray<const Function*>* function_stack,
                             GrowableArray<TokenPosition>* token_positions);
  NOT_IN_PRODUCT(void PrintJSONInlineIntervals(JSONObject* jsobj));
  void DumpInlineIntervals(uword start);
  void DumpSourcePositions(uword start);

  intptr_t GetNullCheckNameIndexAt(int32_t pc_offset);

 private:
  static const TokenPosition& InitialPosition() {
    if (FLAG_precompiled_mode) {
      // In precompiled mode, the CodeSourceMap stores lines instead of
      // real token positions and uses kNoSourcePos for no line information.
      return TokenPosition::kNoSource;
    } else {
      return CodeSourceMapBuilder::kInitialPosition;
    }
  }

  // Reads a TokenPosition value from a CSM, handling the different encoding for
  // when non-symbolic stack traces are enabled.
  static TokenPosition ReadPosition(ReadStream* stream);

  const CodeSourceMap& map_;
  const Array& functions_;
  const Function& root_;

  DISALLOW_COPY_AND_ASSIGN(CodeSourceMapReader);
};

}  // namespace dart

#endif  // RUNTIME_VM_CODE_DESCRIPTORS_H_
