/* -*- 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/. */

#include "jit/shared/Disassembler-shared.h"

#include "jit/JitSpewer.h"
#include "jit/Label.h"
#include "js/Printer.h"

using namespace js::jit;

using js::Sprinter;

#ifdef JS_DISASM_SUPPORTED
// Concurrent assemblers are disambiguated by prefixing every disassembly with a
// tag that is quasi-unique, and certainly unique enough in realistic cases
// where we are debugging and looking at disassembler output.  The tag is a
// letter or digit between brackets prefixing the disassembly, eg, [X]. This
// wraps around every 62 assemblers.
//
// When running with --no-threads we can still have concurrent assemblers in the
// form of nested assemblers, as when an IC stub is created by one assembler
// while a JS compilation is going on and producing output in another assembler.
//
// We generate the tag for an assembler by incrementing a global mod-2^32
// counter every time a new disassembler is created.

mozilla::Atomic<uint32_t> DisassemblerSpew::counter_(0);
#endif

DisassemblerSpew::DisassemblerSpew()
    : printer_(nullptr)
#ifdef JS_DISASM_SUPPORTED
      ,
      labelIndent_(""),
      targetIndent_(""),
      spewNext_(1000),
      nodes_(nullptr),
      tag_(0)
#endif
{
#ifdef JS_DISASM_SUPPORTED
  tag_ = counter_++;
#endif
}

DisassemblerSpew::~DisassemblerSpew() {
#ifdef JS_DISASM_SUPPORTED
  Node* p = nodes_;
  while (p) {
    Node* victim = p;
    p = p->next;
    js_free(victim);
  }
#endif
}

void DisassemblerSpew::setPrinter(Sprinter* printer) { printer_ = printer; }

bool DisassemblerSpew::isDisabled() {
  return !(JitSpewEnabled(JitSpew_Codegen) || printer_);
}

void DisassemblerSpew::spew(const char* fmt, ...) {
#ifdef JS_DISASM_SUPPORTED
  static const char prefix_chars[] =
      "0123456789"
      "abcdefghijklmnopqrstuvwxyz"
      "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  static const char prefix_fmt[] = "[%c] ";

  char fmt2[1024];
  if (sizeof(fmt2) >= strlen(fmt) + sizeof(prefix_fmt)) {
    snprintf(fmt2, sizeof(prefix_fmt), prefix_fmt,
             prefix_chars[tag_ % (sizeof(prefix_chars) - 1)]);
    strcat(fmt2, fmt);
    fmt = fmt2;
  }
#endif

  va_list args;
  va_start(args, fmt);
  spewVA(fmt, args);
  va_end(args);
}

void DisassemblerSpew::spewVA(const char* fmt, va_list va) {
  if (printer_) {
    printer_->vprintf(fmt, va);
    printer_->put("\n");
  }
  js::jit::JitSpewVA(js::jit::JitSpew_Codegen, fmt, va);
}

#ifdef JS_DISASM_SUPPORTED

void DisassemblerSpew::setLabelIndent(const char* s) { labelIndent_ = s; }

void DisassemblerSpew::setTargetIndent(const char* s) { targetIndent_ = s; }

DisassemblerSpew::LabelDoc DisassemblerSpew::refLabel(const Label* l) {
  return l ? LabelDoc(internalResolve(l), l->bound()) : LabelDoc();
}

void DisassemblerSpew::spewRef(const LabelDoc& target) {
  if (isDisabled()) {
    return;
  }
  if (!target.valid) {
    return;
  }
  spew("%s-> %d%s", targetIndent_, target.doc, !target.bound ? "f" : "");
}

void DisassemblerSpew::spewBind(const Label* label) {
  if (isDisabled()) {
    return;
  }
  uint32_t v = internalResolve(label);
  Node* probe = lookup(label);
  if (probe) {
    probe->bound = true;
  }
  spew("%s%d:", labelIndent_, v);
}

void DisassemblerSpew::spewRetarget(const Label* label, const Label* target) {
  if (isDisabled()) {
    return;
  }
  LabelDoc labelDoc = LabelDoc(internalResolve(label), label->bound());
  LabelDoc targetDoc = LabelDoc(internalResolve(target), target->bound());
  Node* probe = lookup(label);
  if (probe) {
    probe->bound = true;
  }
  spew("%s%d: .retarget -> %d%s", labelIndent_, labelDoc.doc, targetDoc.doc,
       !targetDoc.bound ? "f" : "");
}

void DisassemblerSpew::formatLiteral(const LiteralDoc& doc, char* buffer,
                                     size_t bufsize) {
  switch (doc.type) {
    case LiteralDoc::Type::Patchable:
      snprintf(buffer, bufsize, "patchable");
      break;
    case LiteralDoc::Type::I32:
      snprintf(buffer, bufsize, "%d", doc.value.i32);
      break;
    case LiteralDoc::Type::U32:
      snprintf(buffer, bufsize, "%u", doc.value.u32);
      break;
    case LiteralDoc::Type::I64:
      snprintf(buffer, bufsize, "%" PRIi64, doc.value.i64);
      break;
    case LiteralDoc::Type::U64:
      snprintf(buffer, bufsize, "%" PRIu64, doc.value.u64);
      break;
    case LiteralDoc::Type::F32:
      snprintf(buffer, bufsize, "%g", doc.value.f32);
      break;
    case LiteralDoc::Type::F64:
      snprintf(buffer, bufsize, "%g", doc.value.f64);
      break;
    default:
      MOZ_CRASH();
  }
}

void DisassemblerSpew::spewOrphans() {
  for (Node* p = nodes_; p; p = p->next) {
    if (!p->bound) {
      spew("%s%d:    ; .orphan", labelIndent_, p->value);
    }
  }
}

uint32_t DisassemblerSpew::internalResolve(const Label* l) {
  // Note, internalResolve will sometimes return 0 when it is triggered by the
  // profiler and not by a full disassembly, since in that case a label can be
  // used or bound but not previously have been defined.  In that case,
  // internalResolve(l) will not necessarily create a binding for l!
  // Consequently a subsequent lookup(l) may still return null.
  return l->used() || l->bound() ? probe(l) : define(l);
}

uint32_t DisassemblerSpew::probe(const Label* l) {
  Node* n = lookup(l);
  return n ? n->value : 0;
}

uint32_t DisassemblerSpew::define(const Label* l) {
  remove(l);
  uint32_t value = spewNext_++;
  if (!add(l, value)) {
    return 0;
  }
  return value;
}

DisassemblerSpew::Node* DisassemblerSpew::lookup(const Label* key) {
  Node* p;
  for (p = nodes_; p && p->key != key; p = p->next) {
    ;
  }
  return p;
}

DisassemblerSpew::Node* DisassemblerSpew::add(const Label* key,
                                              uint32_t value) {
  MOZ_ASSERT(!lookup(key));
  Node* node = js_new<Node>();
  if (node) {
    node->key = key;
    node->value = value;
    node->bound = false;
    node->next = nodes_;
    nodes_ = node;
  }
  return node;
}

bool DisassemblerSpew::remove(const Label* key) {
  // We do not require that there is a node matching the key.
  for (Node *p = nodes_, *pp = nullptr; p; pp = p, p = p->next) {
    if (p->key == key) {
      if (pp) {
        pp->next = p->next;
      } else {
        nodes_ = p->next;
      }
      js_free(p);
      return true;
    }
  }
  return false;
}

#else

DisassemblerSpew::LabelDoc DisassemblerSpew::refLabel(const Label* l) {
  return LabelDoc();
}

#endif
