/*
 * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

#include "precompiled.hpp"
#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp"
#include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp"
#include "jfr/recorder/repository/jfrChunkWriter.hpp"
#include "jfr/recorder/stacktrace/jfrStackTrace.hpp"
#include "memory/allocation.inline.hpp"
#include "runtime/vframe.hpp"

static void copy_frames(JfrStackFrame** lhs_frames, u4 length, const JfrStackFrame* rhs_frames) {
  assert(lhs_frames != NULL, "invariant");
  assert(rhs_frames != NULL, "invariant");
  if (length > 0) {
    *lhs_frames = NEW_C_HEAP_ARRAY(JfrStackFrame, length, mtTracing);
    memcpy(*lhs_frames, rhs_frames, length * sizeof(JfrStackFrame));
  }
}

JfrStackFrame::JfrStackFrame(const traceid& id, int bci, int type, const Method* method) :
  _method(method), _methodid(id), _line(0), _bci(bci), _type(type) {}

JfrStackFrame::JfrStackFrame(const traceid& id, int bci, int type, int lineno) :
  _method(NULL), _methodid(id), _line(lineno), _bci(bci), _type(type) {}

JfrStackTrace::JfrStackTrace(JfrStackFrame* frames, u4 max_frames) :
  _next(NULL),
  _frames(frames),
  _id(0),
  _hash(0),
  _nr_of_frames(0),
  _max_frames(max_frames),
  _frames_ownership(false),
  _reached_root(false),
  _lineno(false),
  _written(false) {}

JfrStackTrace::JfrStackTrace(traceid id, const JfrStackTrace& trace, const JfrStackTrace* next) :
  _next(next),
  _frames(NULL),
  _id(id),
  _hash(trace._hash),
  _nr_of_frames(trace._nr_of_frames),
  _max_frames(trace._max_frames),
  _frames_ownership(true),
  _reached_root(trace._reached_root),
  _lineno(trace._lineno),
  _written(false) {
  copy_frames(&_frames, trace._nr_of_frames, trace._frames);
}

JfrStackTrace::~JfrStackTrace() {
  if (_frames_ownership) {
    FREE_C_HEAP_ARRAY(JfrStackFrame, _frames, mtTracing);
  }
}

template <typename Writer>
static void write_stacktrace(Writer& w, traceid id, bool reached_root, u4 nr_of_frames, const JfrStackFrame* frames) {
  w.write((u8)id);
  w.write((u1)!reached_root);
  w.write(nr_of_frames);
  for (u4 i = 0; i < nr_of_frames; ++i) {
    frames[i].write(w);
  }
}

void JfrStackTrace::write(JfrChunkWriter& sw) const {
  assert(!_written, "invariant");
  write_stacktrace(sw, _id, _reached_root, _nr_of_frames, _frames);
  _written = true;
}

void JfrStackTrace::write(JfrCheckpointWriter& cpw) const {
  write_stacktrace(cpw, _id, _reached_root, _nr_of_frames, _frames);
}

bool JfrStackFrame::equals(const JfrStackFrame& rhs) const {
  return _methodid == rhs._methodid && _bci == rhs._bci && _type == rhs._type;
}

bool JfrStackTrace::equals(const JfrStackTrace& rhs) const {
  if (_reached_root != rhs._reached_root || _nr_of_frames != rhs._nr_of_frames || _hash != rhs._hash) {
    return false;
  }
  for (u4 i = 0; i < _nr_of_frames; ++i) {
    if (!_frames[i].equals(rhs._frames[i])) {
      return false;
    }
  }
  return true;
}

template <typename Writer>
static void write_frame(Writer& w, traceid methodid, int line, int bci, u1 type) {
  w.write((u8)methodid);
  w.write((u4)line);
  w.write((u4)bci);
  w.write((u8)type);
}

void JfrStackFrame::write(JfrChunkWriter& cw) const {
  write_frame(cw, _methodid, _line, _bci, _type);
}

void JfrStackFrame::write(JfrCheckpointWriter& cpw) const {
  write_frame(cpw, _methodid, _line, _bci, _type);
}

class vframeStreamSamples : public vframeStreamCommon {
 public:
  // constructor that starts with sender of frame fr (top_frame)
  vframeStreamSamples(JavaThread *jt, frame fr, bool stop_at_java_call_stub) : vframeStreamCommon(jt) {
    _stop_at_java_call_stub = stop_at_java_call_stub;
    _frame = fr;

    // We must always have a valid frame to start filling
    bool filled_in = fill_from_frame();
    assert(filled_in, "invariant");
  }
  void samples_next();
  void stop() {}
};

// Solaris SPARC Compiler1 needs an additional check on the grandparent
// of the top_frame when the parent of the top_frame is interpreted and
// the grandparent is compiled. However, in this method we do not know
// the relationship of the current _frame relative to the top_frame so
// we implement a more broad sanity check. When the previous callee is
// interpreted and the current sender is compiled, we verify that the
// current sender is also walkable. If it is not walkable, then we mark
// the current vframeStream as at the end.
void vframeStreamSamples::samples_next() {
  // handle frames with inlining
  if (_mode == compiled_mode &&
    vframeStreamCommon::fill_in_compiled_inlined_sender()) {
    return;
  }

  // handle general case
  u4 loop_count = 0;
  u4 loop_max = MAX_STACK_DEPTH * 2;
  do {
    loop_count++;
    // By the time we get here we should never see unsafe but better safe then segv'd
    if (loop_count > loop_max || !_frame.safe_for_sender(_thread)) {
      _mode = at_end_mode;
      return;
    }
    _frame = _frame.sender(&_reg_map);
  } while (!fill_from_frame());
}

bool JfrStackTrace::record_thread(JavaThread& thread, frame& frame) {
  vframeStreamSamples st(&thread, frame, false);
  u4 count = 0;
  _reached_root = true;

  while (!st.at_end()) {
    if (count >= _max_frames) {
      _reached_root = false;
      break;
    }
    const Method* method = st.method();
    if (!method->is_valid_method()) {
      // we throw away everything we've gathered in this sample since
      // none of it is safe
      return false;
    }
    const traceid mid = JfrTraceId::use(method);
    int type = st.is_interpreted_frame() ? JfrStackFrame::FRAME_INTERPRETER : JfrStackFrame::FRAME_JIT;
    int bci = 0;
    if (method->is_native()) {
      type = JfrStackFrame::FRAME_NATIVE;
    } else {
      bci = st.bci();
    }
    const int lineno = method->line_number_from_bci(bci);
    // Can we determine if it's inlined?
    _hash = (_hash << 2) + (unsigned int)(((size_t)mid >> 2) + (bci << 4) + type);
    _frames[count] = JfrStackFrame(mid, bci, type, method);
    st.samples_next();
    count++;
  }

  _lineno = true;
  _nr_of_frames = count;
  return true;
}

void JfrStackFrame::resolve_lineno() const {
  assert(_method, "no method pointer");
  assert(_line == 0, "already have linenumber");
  _line = _method->line_number_from_bci(_bci);
}

void JfrStackTrace::resolve_linenos() const {
  for (unsigned int i = 0; i < _nr_of_frames; i++) {
    _frames[i].resolve_lineno();
  }
  _lineno = true;
}

bool JfrStackTrace::record_safe(JavaThread* thread, int skip, StackWalkMode mode) {
  bool success = false;
  switch(mode) {
    case WALK_BY_DEFAULT:
      {
        vframeStream vfs(thread);
        success = fill_in(vfs, skip, mode);
        break;
      }
    case WALK_BY_CURRENT_FRAME:
      {
        vframeStream vfs(thread, os::current_frame());
        success = fill_in(vfs, skip, mode);
        break;
      }
    default:
      ShouldNotReachHere();
  }
  return success;
}

bool JfrStackTrace::fill_in(vframeStream& vfs, int skip, StackWalkMode mode) {
  u4 count = 0;
  _reached_root = true;
  // Indicates whether the top frame is visited in this frames iteration.
  // Top frame bci may be invalid and fill_in() will fix the top frame bci in a conservative way.
  bool top_frame_visited = false;
  for (int i = 0; i < skip; i++) {
    if (vfs.at_end()) {
      break;
    }
    // The top frame is in skip list.
    // Mark top_frame_visited to avoid unnecessary top frame bci fixing.
    if (!top_frame_visited) {
      top_frame_visited = true;
    }
    vfs.next();
  }

  while (!vfs.at_end()) {
    if (count >= _max_frames) {
      _reached_root = false;
      break;
    }
    const Method* method = vfs.method();
    const traceid mid = JfrTraceId::use(method);
    int type = vfs.is_interpreted_frame() ? JfrStackFrame::FRAME_INTERPRETER : JfrStackFrame::FRAME_JIT;
    int bci = 0;
    if (method->is_native()) {
      type = JfrStackFrame::FRAME_NATIVE;
      // The top frame is in native.
      // Mark top_frame_visited to avoid unnecessary top frame bci fixing.
      if (!top_frame_visited) {
        top_frame_visited = true;
      }
    }
    else {
      bci = vfs.bci();
      // Hit the top frame and fix bci here.
      if (!top_frame_visited) {
        if (mode == WALK_BY_CURRENT_FRAME) {
          // Only fix opto fast path allocation.
          // All fast path allocations do not have cached event id.
          if (!vfs.thread_ref()->jfr_thread_local()->has_cached_event_id()) {
            assert(vfs.thread_ref()->jfr_thread_local()->has_cached_top_frame_bci(), "Invariant");
            bci = vfs.thread_ref()->jfr_thread_local()->cached_top_frame_bci();
          }
        }
        top_frame_visited = true;
      }
    }
    // Can we determine if it's inlined?
    _hash = (_hash << 2) + (unsigned int)(((size_t)mid >> 2) + (bci << 4) + type);
    _frames[count] = JfrStackFrame(mid, bci, type, method);
    vfs.next();
    count++;
  }

  _nr_of_frames = count;
  return true;
}

