// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/v8.h"
#include "test/cctest/cctest.h"

#include "src/compiler/code-generator.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/graph.h"
#include "src/compiler/instruction-selector.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/node.h"
#include "src/compiler/operator.h"
#include "src/compiler/raw-machine-assembler.h"
#include "src/compiler/register-allocator.h"
#include "src/compiler/schedule.h"

#include "src/full-codegen.h"
#include "src/parser.h"
#include "src/rewriter.h"

#include "test/cctest/compiler/function-tester.h"

using namespace v8::internal;
using namespace v8::internal::compiler;


#if V8_TURBOFAN_TARGET

typedef RawMachineAssembler::Label MLabel;

static Handle<JSFunction> NewFunction(const char* source) {
  return v8::Utils::OpenHandle(
      *v8::Handle<v8::Function>::Cast(CompileRun(source)));
}


class DeoptCodegenTester {
 public:
  explicit DeoptCodegenTester(HandleAndZoneScope* scope, const char* src)
      : scope_(scope),
        function(NewFunction(src)),
        info(function, scope->main_zone()),
        bailout_id(-1) {
    CHECK(Parser::Parse(&info));
    StrictMode strict_mode = info.function()->strict_mode();
    info.SetStrictMode(strict_mode);
    info.SetOptimizing(BailoutId::None(), Handle<Code>(function->code()));
    CHECK(Rewriter::Rewrite(&info));
    CHECK(Scope::Analyze(&info));
    CHECK_NE(NULL, info.scope());
    Handle<ScopeInfo> scope_info = ScopeInfo::Create(info.scope(), info.zone());
    info.shared_info()->set_scope_info(*scope_info);

    FunctionTester::EnsureDeoptimizationSupport(&info);

    DCHECK(info.shared_info()->has_deoptimization_support());

    graph = new (scope_->main_zone()) Graph(scope_->main_zone());
  }

  virtual ~DeoptCodegenTester() { delete code; }

  void GenerateCodeFromSchedule(Schedule* schedule) {
    OFStream os(stdout);
    os << *schedule;

    // Initialize the codegen and generate code.
    Linkage* linkage = new (scope_->main_zone()) Linkage(&info);
    code = new v8::internal::compiler::InstructionSequence(linkage, graph,
                                                           schedule);
    SourcePositionTable source_positions(graph);
    InstructionSelector selector(code, &source_positions);
    selector.SelectInstructions();

    os << "----- Instruction sequence before register allocation -----\n"
       << *code;

    RegisterAllocator allocator(code);
    CHECK(allocator.Allocate());

    os << "----- Instruction sequence after register allocation -----\n"
       << *code;

    compiler::CodeGenerator generator(code);
    result_code = generator.GenerateCode();

#ifdef DEBUG
    result_code->Print();
#endif
  }

  Zone* zone() { return scope_->main_zone(); }

  HandleAndZoneScope* scope_;
  Handle<JSFunction> function;
  CompilationInfo info;
  BailoutId bailout_id;
  Handle<Code> result_code;
  v8::internal::compiler::InstructionSequence* code;
  Graph* graph;
};


class TrivialDeoptCodegenTester : public DeoptCodegenTester {
 public:
  explicit TrivialDeoptCodegenTester(HandleAndZoneScope* scope)
      : DeoptCodegenTester(scope,
                           "function foo() { deopt(); return 42; }; foo") {}

  void GenerateCode() {
    GenerateCodeFromSchedule(BuildGraphAndSchedule(graph));
  }

  Schedule* BuildGraphAndSchedule(Graph* graph) {
    Isolate* isolate = info.isolate();
    CommonOperatorBuilder common(zone());

    // Manually construct a schedule for the function below:
    // function foo() {
    //   deopt();
    // }

    MachineType parameter_reps[] = {kMachAnyTagged};
    MachineCallDescriptorBuilder descriptor_builder(kMachAnyTagged, 1,
                                                    parameter_reps);

    RawMachineAssembler m(graph, &descriptor_builder);

    Handle<Object> undef_object =
        Handle<Object>(isolate->heap()->undefined_value(), isolate);
    PrintableUnique<Object> undef_constant =
        PrintableUnique<Object>::CreateUninitialized(zone(), undef_object);
    Node* undef_node = m.NewNode(common.HeapConstant(undef_constant));

    Handle<JSFunction> deopt_function =
        NewFunction("function deopt() { %DeoptimizeFunction(foo); }; deopt");
    PrintableUnique<Object> deopt_fun_constant =
        PrintableUnique<Object>::CreateUninitialized(zone(), deopt_function);
    Node* deopt_fun_node = m.NewNode(common.HeapConstant(deopt_fun_constant));

    MLabel deopt, cont;
    Node* call = m.CallJS0(deopt_fun_node, undef_node, &cont, &deopt);

    m.Bind(&cont);
    m.NewNode(common.Continuation(), call);
    m.Return(undef_node);

    m.Bind(&deopt);
    m.NewNode(common.LazyDeoptimization(), call);

    bailout_id = GetCallBailoutId();
    Node* parameters = m.NewNode(common.StateValues(1), undef_node);
    Node* locals = m.NewNode(common.StateValues(0));
    Node* stack = m.NewNode(common.StateValues(0));

    Node* state_node =
        m.NewNode(common.FrameState(bailout_id), parameters, locals, stack);
    m.Deoptimize(state_node);

    // Schedule the graph:
    Schedule* schedule = m.Export();

    cont_block = cont.block();
    deopt_block = deopt.block();

    return schedule;
  }

  BailoutId GetCallBailoutId() {
    ZoneList<Statement*>* body = info.function()->body();
    for (int i = 0; i < body->length(); i++) {
      if (body->at(i)->IsExpressionStatement() &&
          body->at(i)->AsExpressionStatement()->expression()->IsCall()) {
        return body->at(i)->AsExpressionStatement()->expression()->id();
      }
    }
    CHECK(false);
    return BailoutId(-1);
  }

  BasicBlock* cont_block;
  BasicBlock* deopt_block;
};


TEST(TurboTrivialDeoptCodegen) {
  HandleAndZoneScope scope;
  InitializedHandleScope handles;

  FLAG_allow_natives_syntax = true;
  FLAG_turbo_deoptimization = true;

  TrivialDeoptCodegenTester t(&scope);
  t.GenerateCode();

  DeoptimizationInputData* data =
      DeoptimizationInputData::cast(t.result_code->deoptimization_data());

  Label* cont_label = t.code->GetLabel(t.cont_block);
  Label* deopt_label = t.code->GetLabel(t.deopt_block);

  // Check the safepoint - it should contain an entry for the call
  // with the right deoptimization address.
  SafepointEntry entry = t.result_code->GetSafepointEntry(
      t.result_code->instruction_start() + cont_label->pos());
  CHECK(entry.is_valid());
  CHECK_EQ(deopt_label->pos(), entry.deoptimization_pc());

  // Check that we deoptimize to the right AST id.
  CHECK_EQ(1, data->DeoptCount());
  CHECK_EQ(t.bailout_id.ToInt(), data->AstId(0).ToInt());
}


TEST(TurboTrivialDeoptCodegenAndRun) {
  HandleAndZoneScope scope;
  InitializedHandleScope handles;

  FLAG_allow_natives_syntax = true;
  FLAG_turbo_deoptimization = true;

  TrivialDeoptCodegenTester t(&scope);
  t.GenerateCode();

  t.function->ReplaceCode(*t.result_code);
  t.info.context()->native_context()->AddOptimizedCode(*t.result_code);

  Isolate* isolate = scope.main_isolate();
  Handle<Object> result;
  bool has_pending_exception =
      !Execution::Call(isolate, t.function,
                       isolate->factory()->undefined_value(), 0, NULL,
                       false).ToHandle(&result);
  CHECK(!has_pending_exception);
  CHECK(result->SameValue(Smi::FromInt(42)));
}


class TrivialRuntimeDeoptCodegenTester : public DeoptCodegenTester {
 public:
  explicit TrivialRuntimeDeoptCodegenTester(HandleAndZoneScope* scope)
      : DeoptCodegenTester(
            scope,
            "function foo() { %DeoptimizeFunction(foo); return 42; }; foo") {}

  void GenerateCode() {
    GenerateCodeFromSchedule(BuildGraphAndSchedule(graph));
  }

  Schedule* BuildGraphAndSchedule(Graph* graph) {
    Isolate* isolate = info.isolate();
    CommonOperatorBuilder common(zone());

    // Manually construct a schedule for the function below:
    // function foo() {
    //   %DeoptimizeFunction(foo);
    // }

    MachineType parameter_reps[] = {kMachAnyTagged};
    MachineCallDescriptorBuilder descriptor_builder(kMachAnyTagged, 2,
                                                    parameter_reps);

    RawMachineAssembler m(graph, &descriptor_builder);

    Handle<Object> undef_object =
        Handle<Object>(isolate->heap()->undefined_value(), isolate);
    PrintableUnique<Object> undef_constant =
        PrintableUnique<Object>::CreateUninitialized(zone(), undef_object);
    Node* undef_node = m.NewNode(common.HeapConstant(undef_constant));

    PrintableUnique<Object> this_fun_constant =
        PrintableUnique<Object>::CreateUninitialized(zone(), function);
    Node* this_fun_node = m.NewNode(common.HeapConstant(this_fun_constant));

    MLabel deopt, cont;
    Node* call = m.CallRuntime1(Runtime::kDeoptimizeFunction, this_fun_node,
                                &cont, &deopt);

    m.Bind(&cont);
    m.NewNode(common.Continuation(), call);
    m.Return(undef_node);

    m.Bind(&deopt);
    m.NewNode(common.LazyDeoptimization(), call);

    bailout_id = GetCallBailoutId();
    Node* parameters = m.NewNode(common.StateValues(1), undef_node);
    Node* locals = m.NewNode(common.StateValues(0));
    Node* stack = m.NewNode(common.StateValues(0));

    Node* state_node =
        m.NewNode(common.FrameState(bailout_id), parameters, locals, stack);
    m.Deoptimize(state_node);

    // Schedule the graph:
    Schedule* schedule = m.Export();

    cont_block = cont.block();
    deopt_block = deopt.block();

    return schedule;
  }

  BailoutId GetCallBailoutId() {
    ZoneList<Statement*>* body = info.function()->body();
    for (int i = 0; i < body->length(); i++) {
      if (body->at(i)->IsExpressionStatement() &&
          body->at(i)->AsExpressionStatement()->expression()->IsCallRuntime()) {
        return body->at(i)->AsExpressionStatement()->expression()->id();
      }
    }
    CHECK(false);
    return BailoutId(-1);
  }

  BasicBlock* cont_block;
  BasicBlock* deopt_block;
};


TEST(TurboTrivialRuntimeDeoptCodegenAndRun) {
  HandleAndZoneScope scope;
  InitializedHandleScope handles;

  FLAG_allow_natives_syntax = true;
  FLAG_turbo_deoptimization = true;

  TrivialRuntimeDeoptCodegenTester t(&scope);
  t.GenerateCode();

  t.function->ReplaceCode(*t.result_code);
  t.info.context()->native_context()->AddOptimizedCode(*t.result_code);

  Isolate* isolate = scope.main_isolate();
  Handle<Object> result;
  bool has_pending_exception =
      !Execution::Call(isolate, t.function,
                       isolate->factory()->undefined_value(), 0, NULL,
                       false).ToHandle(&result);
  CHECK(!has_pending_exception);
  CHECK(result->SameValue(Smi::FromInt(42)));
}

#endif
