/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef jit_shared_Disassembler_shared_h
#define jit_shared_Disassembler_shared_h

#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"

#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>

#include "jstypes.h"  // JS_PUBLIC_API

#if defined(JS_DISASM_ARM) || defined(JS_DISASM_ARM64)
#  define JS_DISASM_SUPPORTED
#endif

namespace js {

class JS_PUBLIC_API Sprinter;

namespace jit {

class Label;

// A wrapper around spew/disassembly functionality.  The disassembler is built
// on a per-instruction disassembler (as in our ARM, ARM64 back-ends) and
// formats labels with meaningful names and literals with meaningful values, if
// the assembler creates documentation (with provided helpers) at appropriate
// points.

class DisassemblerSpew {
#ifdef JS_DISASM_SUPPORTED
  struct Node {
    const Label* key;  // Never dereferenced, only used for its value
    uint32_t value;    // The printable label value
    bool bound;        // If the label has been seen by spewBind()
    Node* next;
  };

  Node* lookup(const Label* key);
  Node* add(const Label* key, uint32_t value);
  bool remove(const Label* key);

  uint32_t probe(const Label* l);
  uint32_t define(const Label* l);
  uint32_t internalResolve(const Label* l);
#endif

  void spewVA(const char* fmt, va_list args) MOZ_FORMAT_PRINTF(2, 0);

 public:
  DisassemblerSpew();
  ~DisassemblerSpew();

#ifdef JS_DISASM_SUPPORTED
  // Set indentation strings.  The spewer retains a reference to s.
  void setLabelIndent(const char* s);
  void setTargetIndent(const char* s);
#endif

  // Set the spew printer, which will always be used if it is set, regardless
  // of whether the system spew channel is enabled or not.  The spewer retains
  // a reference to sp.
  void setPrinter(Sprinter* sp);

  // Return true if disassembly spew is disabled and no additional printer is
  // set.
  bool isDisabled();

  // Format and print text on the spew channel; output is suppressed if spew
  // is disabled.  The output is not indented, and is terminated by a newline.
  void spew(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);

  // Documentation for a label reference.
  struct LabelDoc {
#ifdef JS_DISASM_SUPPORTED
    LabelDoc() : doc(0), bound(false), valid(false) {}
    LabelDoc(uint32_t doc, bool bound) : doc(doc), bound(bound), valid(true) {}
    const uint32_t doc;
    const bool bound;
    const bool valid;
#else
    LabelDoc() = default;
    LabelDoc(uint32_t, bool) {}
#endif
  };

  // Documentation for a literal load.
  struct LiteralDoc {
#ifdef JS_DISASM_SUPPORTED
    enum class Type { Patchable, I32, U32, I64, U64, F32, F64 };
    const Type type;
    union {
      int32_t i32;
      uint32_t u32;
      int64_t i64;
      uint64_t u64;
      float f32;
      double f64;
    } value;
    LiteralDoc() : type(Type::Patchable) {}
    explicit LiteralDoc(int32_t v) : type(Type::I32) { value.i32 = v; }
    explicit LiteralDoc(uint32_t v) : type(Type::U32) { value.u32 = v; }
    explicit LiteralDoc(int64_t v) : type(Type::I64) { value.i64 = v; }
    explicit LiteralDoc(uint64_t v) : type(Type::U64) { value.u64 = v; }
    explicit LiteralDoc(float v) : type(Type::F32) { value.f32 = v; }
    explicit LiteralDoc(double v) : type(Type::F64) { value.f64 = v; }
#else
    LiteralDoc() = default;
    explicit LiteralDoc(int32_t) {}
    explicit LiteralDoc(uint32_t) {}
    explicit LiteralDoc(int64_t) {}
    explicit LiteralDoc(uint64_t) {}
    explicit LiteralDoc(float) {}
    explicit LiteralDoc(double) {}
#endif
  };

  // Reference a label, resolving it to a printable representation.
  //
  // NOTE: The printable representation depends on the state of the label, so
  // if we call resolve() when emitting & disassembling a branch instruction
  // then it should be called before the label becomes Used, if emitting the
  // branch can change the label's state.
  //
  // If the disassembler is not defined this returns a structure that is
  // marked not valid.
  LabelDoc refLabel(const Label* l);

#ifdef JS_DISASM_SUPPORTED
  // Spew the label information previously gathered by refLabel(), at a point
  // where the label is referenced.  The output is indented by targetIndent_
  // and terminated by a newline.
  void spewRef(const LabelDoc& target);

  // Spew the label at the point where the label is bound.  The output is
  // indented by labelIndent_ and terminated by a newline.
  void spewBind(const Label* label);

  // Spew a retarget directive at the point where the retarget is recorded.
  // The output is indented by labelIndent_ and terminated by a newline.
  void spewRetarget(const Label* label, const Label* target);

  // Format a literal value into the buffer.  The buffer is always
  // NUL-terminated even if this chops the formatted value.
  void formatLiteral(const LiteralDoc& doc, char* buffer, size_t bufsize);

  // Print any unbound labels, one per line, with normal label indent and with
  // a comment indicating the label is not defined.  Labels can be referenced
  // but unbound in some legitimate cases, normally for traps.  Printing them
  // reduces confusion.
  void spewOrphans();
#endif

 private:
  Sprinter* printer_;
#ifdef JS_DISASM_SUPPORTED
  const char* labelIndent_;
  const char* targetIndent_;
  uint32_t spewNext_;
  Node* nodes_;
  uint32_t tag_;

  // This global is used to disambiguate concurrently live assemblers, see
  // comments in Disassembler-shared.cpp for why this is desirable.
  //
  // The variable is atomic to avoid any kind of complaint from thread
  // sanitizers etc.  However, trying to look at disassembly without using
  // --no-threads is basically insane, so you can ignore the multi-threading
  // implications here.
  static mozilla::Atomic<uint32_t> counter_;
#endif
};

}  // namespace jit
}  // namespace js

#endif  // jit_shared_Disassembler_shared_h
