// Copyright 2006-2009 the V8 project authors. All rights reserved.
//
// Tests of profiler-related functions from log.h

#ifdef ENABLE_LOGGING_AND_PROFILING

#include <stdlib.h>

#include "v8.h"

#include "codegen.h"
#include "log.h"
#include "top.h"
#include "cctest.h"
#include "disassembler.h"
#include "register-allocator-inl.h"

using v8::Function;
using v8::Local;
using v8::Object;
using v8::Script;
using v8::String;
using v8::Value;

using v8::internal::byte;
using v8::internal::Address;
using v8::internal::Handle;
using v8::internal::JSFunction;
using v8::internal::StackTracer;
using v8::internal::TickSample;
using v8::internal::Top;

namespace i = v8::internal;


static v8::Persistent<v8::Context> env;


static struct {
  TickSample* sample;
} trace_env = { NULL };


static void InitTraceEnv(TickSample* sample) {
  trace_env.sample = sample;
}


static void DoTrace(Address fp) {
  trace_env.sample->fp = fp;
  // sp is only used to define stack high bound
  trace_env.sample->sp =
      reinterpret_cast<Address>(trace_env.sample) - 10240;
  StackTracer::Trace(trace_env.sample);
}


// Hide c_entry_fp to emulate situation when sampling is done while
// pure JS code is being executed
static void DoTraceHideCEntryFPAddress(Address fp) {
  v8::internal::Address saved_c_frame_fp = *(Top::c_entry_fp_address());
  CHECK(saved_c_frame_fp);
  *(Top::c_entry_fp_address()) = 0;
  DoTrace(fp);
  *(Top::c_entry_fp_address()) = saved_c_frame_fp;
}


static void CheckRetAddrIsInFunction(const char* func_name,
                                     Address ret_addr,
                                     Address func_start_addr,
                                     unsigned int func_len) {
  printf("CheckRetAddrIsInFunction \"%s\": %p %p %p\n",
         func_name, func_start_addr, ret_addr, func_start_addr + func_len);
  CHECK_GE(ret_addr, func_start_addr);
  CHECK_GE(func_start_addr + func_len, ret_addr);
}


static void CheckRetAddrIsInJSFunction(const char* func_name,
                                       Address ret_addr,
                                       Handle<JSFunction> func) {
  v8::internal::Code* func_code = func->code();
  CheckRetAddrIsInFunction(
      func_name, ret_addr,
      func_code->instruction_start(),
      func_code->ExecutableSize());
}


// --- T r a c e   E x t e n s i o n ---

class TraceExtension : public v8::Extension {
 public:
  TraceExtension() : v8::Extension("v8/trace", kSource) { }
  virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction(
      v8::Handle<String> name);
  static v8::Handle<v8::Value> Trace(const v8::Arguments& args);
  static v8::Handle<v8::Value> JSTrace(const v8::Arguments& args);
  static v8::Handle<v8::Value> JSEntrySP(const v8::Arguments& args);
  static v8::Handle<v8::Value> JSEntrySPLevel2(const v8::Arguments& args);
 private:
  static Address GetFP(const v8::Arguments& args);
  static const char* kSource;
};


const char* TraceExtension::kSource =
    "native function trace();"
    "native function js_trace();"
    "native function js_entry_sp();"
    "native function js_entry_sp_level2();";

v8::Handle<v8::FunctionTemplate> TraceExtension::GetNativeFunction(
    v8::Handle<String> name) {
  if (name->Equals(String::New("trace"))) {
    return v8::FunctionTemplate::New(TraceExtension::Trace);
  } else if (name->Equals(String::New("js_trace"))) {
    return v8::FunctionTemplate::New(TraceExtension::JSTrace);
  } else if (name->Equals(String::New("js_entry_sp"))) {
    return v8::FunctionTemplate::New(TraceExtension::JSEntrySP);
  } else if (name->Equals(String::New("js_entry_sp_level2"))) {
    return v8::FunctionTemplate::New(TraceExtension::JSEntrySPLevel2);
  } else {
    CHECK(false);
    return v8::Handle<v8::FunctionTemplate>();
  }
}


Address TraceExtension::GetFP(const v8::Arguments& args) {
  CHECK_EQ(1, args.Length());
  // CodeGenerator::GenerateGetFramePointer pushes EBP / RBP value
  // on stack. In 64-bit mode we can't use Smi operations code because
  // they check that value is within Smi bounds.
  Address fp = *reinterpret_cast<Address*>(*args[0]);
  printf("Trace: %p\n", fp);
  return fp;
}


v8::Handle<v8::Value> TraceExtension::Trace(const v8::Arguments& args) {
  DoTrace(GetFP(args));
  return v8::Undefined();
}


v8::Handle<v8::Value> TraceExtension::JSTrace(const v8::Arguments& args) {
  DoTraceHideCEntryFPAddress(GetFP(args));
  return v8::Undefined();
}


static Address GetJsEntrySp() {
  CHECK_NE(NULL, Top::GetCurrentThread());
  return Top::js_entry_sp(Top::GetCurrentThread());
}


v8::Handle<v8::Value> TraceExtension::JSEntrySP(const v8::Arguments& args) {
  CHECK_NE(0, GetJsEntrySp());
  return v8::Undefined();
}


v8::Handle<v8::Value> TraceExtension::JSEntrySPLevel2(
    const v8::Arguments& args) {
  v8::HandleScope scope;
  const Address js_entry_sp = GetJsEntrySp();
  CHECK_NE(0, js_entry_sp);
  CompileRun("js_entry_sp();");
  CHECK_EQ(js_entry_sp, GetJsEntrySp());
  return v8::Undefined();
}


static TraceExtension kTraceExtension;
v8::DeclareExtension kTraceExtensionDeclaration(&kTraceExtension);


static void InitializeVM() {
  if (env.IsEmpty()) {
    v8::HandleScope scope;
    const char* extensions[] = { "v8/trace" };
    v8::ExtensionConfiguration config(1, extensions);
    env = v8::Context::New(&config);
  }
  v8::HandleScope scope;
  env->Enter();
}


static Handle<JSFunction> CompileFunction(const char* source) {
  return v8::Utils::OpenHandle(*Script::Compile(String::New(source)));
}


static Local<Value> GetGlobalProperty(const char* name) {
  return env->Global()->Get(String::New(name));
}


static Handle<JSFunction> GetGlobalJSFunction(const char* name) {
  Handle<JSFunction> js_func(JSFunction::cast(
                                 *(v8::Utils::OpenHandle(
                                       *GetGlobalProperty(name)))));
  return js_func;
}


static void CheckRetAddrIsInJSFunction(const char* func_name,
                                       Address ret_addr) {
  CheckRetAddrIsInJSFunction(func_name, ret_addr,
                             GetGlobalJSFunction(func_name));
}


static void SetGlobalProperty(const char* name, Local<Value> value) {
  env->Global()->Set(String::New(name), value);
}


static Handle<v8::internal::String> NewString(const char* s) {
  return i::Factory::NewStringFromAscii(i::CStrVector(s));
}


namespace v8 {
namespace internal {

class CodeGeneratorPatcher {
 public:
  CodeGeneratorPatcher() {
    CodeGenerator::InlineRuntimeLUT genGetFramePointer =
        {&CodeGenerator::GenerateGetFramePointer, "_GetFramePointer"};
    // _FastCharCodeAt is not used in our tests.
    bool result = CodeGenerator::PatchInlineRuntimeEntry(
        NewString("_FastCharCodeAt"),
        genGetFramePointer, &oldInlineEntry);
    CHECK(result);
  }

  ~CodeGeneratorPatcher() {
    CHECK(CodeGenerator::PatchInlineRuntimeEntry(
        NewString("_GetFramePointer"),
        oldInlineEntry, NULL));
  }

 private:
  CodeGenerator::InlineRuntimeLUT oldInlineEntry;
};

} }  // namespace v8::internal


// Creates a global function named 'func_name' that calls the tracing
// function 'trace_func_name' with an actual EBP register value,
// shifted right to be presented as Smi.
static void CreateTraceCallerFunction(const char* func_name,
                                      const char* trace_func_name) {
  i::EmbeddedVector<char, 256> trace_call_buf;
  i::OS::SNPrintF(trace_call_buf, "%s(%%_GetFramePointer());", trace_func_name);

  // Compile the script.
  i::CodeGeneratorPatcher patcher;
  bool allow_natives_syntax = i::FLAG_allow_natives_syntax;
  i::FLAG_allow_natives_syntax = true;
  Handle<JSFunction> func = CompileFunction(trace_call_buf.start());
  CHECK(!func.is_null());
  i::FLAG_allow_natives_syntax = allow_natives_syntax;

#ifdef DEBUG
  v8::internal::Code* func_code = func->code();
  CHECK(func_code->IsCode());
  func_code->Print();
#endif

  SetGlobalProperty(func_name, v8::ToApi<Value>(func));
}


TEST(CFromJSStackTrace) {
  TickSample sample;
  InitTraceEnv(&sample);

  InitializeVM();
  v8::HandleScope scope;
  CreateTraceCallerFunction("JSFuncDoTrace", "trace");
  CompileRun(
      "function JSTrace() {"
      "         JSFuncDoTrace();"
      "};\n"
      "JSTrace();");
  CHECK_GT(sample.frames_count, 1);
  // Stack sampling will start from the first JS function, i.e. "JSFuncDoTrace"
  CheckRetAddrIsInJSFunction("JSFuncDoTrace",
                             sample.stack[0]);
  CheckRetAddrIsInJSFunction("JSTrace",
                             sample.stack[1]);
}


TEST(PureJSStackTrace) {
  TickSample sample;
  InitTraceEnv(&sample);

  InitializeVM();
  v8::HandleScope scope;
  CreateTraceCallerFunction("JSFuncDoTrace", "js_trace");
  CompileRun(
      "function JSTrace() {"
      "         JSFuncDoTrace();"
      "};\n"
      "function OuterJSTrace() {"
      "         JSTrace();"
      "};\n"
      "OuterJSTrace();");
  // The last JS function called.
  CHECK_EQ(GetGlobalJSFunction("JSFuncDoTrace")->address(),
           sample.function);
  CHECK_GT(sample.frames_count, 1);
  // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
  CheckRetAddrIsInJSFunction("JSTrace",
                             sample.stack[0]);
  CheckRetAddrIsInJSFunction("OuterJSTrace",
                             sample.stack[1]);
}


static void CFuncDoTrace(byte dummy_parameter) {
  Address fp;
#ifdef __GNUC__
  fp = reinterpret_cast<Address>(__builtin_frame_address(0));
#elif defined _MSC_VER
  // Approximate a frame pointer address. We compile without base pointers,
  // so we can't trust ebp/rbp.
  fp = &dummy_parameter - 2 * sizeof(void*);  // NOLINT
#else
#error Unexpected platform.
#endif
  DoTrace(fp);
}


static int CFunc(int depth) {
  if (depth <= 0) {
    CFuncDoTrace(0);
    return 0;
  } else {
    return CFunc(depth - 1) + 1;
  }
}


TEST(PureCStackTrace) {
  TickSample sample;
  InitTraceEnv(&sample);
  // Check that sampler doesn't crash
  CHECK_EQ(10, CFunc(10));
}


TEST(JsEntrySp) {
  InitializeVM();
  v8::HandleScope scope;
  CHECK_EQ(0, GetJsEntrySp());
  CompileRun("a = 1; b = a + 1;");
  CHECK_EQ(0, GetJsEntrySp());
  CompileRun("js_entry_sp();");
  CHECK_EQ(0, GetJsEntrySp());
  CompileRun("js_entry_sp_level2();");
  CHECK_EQ(0, GetJsEntrySp());
}

#endif  // ENABLE_LOGGING_AND_PROFILING