// Copyright 2020 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.

// Flags: --allow-natives-syntax --super-ic --opt
// Flags: --no-always-opt --no-stress-opt --deopt-every-n-times=0

// This file contains tests which are disabled for TurboProp. TurboProp deopts
// differently than TurboFan, so the assertions about when a function is
// deoptimized won't hold. In addition, TurboProp doesn't inline, so all
// tests that rely on inlining need to be in this file.

(function TestPropertyIsConstant() {
  // Test for a case where the property is a constant found in the lookup start
  // object.
  class A {}
  A.prototype.bar = "wrong value: A.prototype.bar";

  class B extends A {};
  B.prototype.bar = "correct value";

  class C extends B {
    foo() { return super.bar; }
  }
  C.prototype.bar = "wrong value: C.prototype.bar";
  %PrepareFunctionForOptimization(C.prototype.foo);

  let o = new C();
  o.bar = "wrong value: o.bar";

  // Fill in the feedback.
  r = o.foo();
  assertEquals("correct value", r);

  %OptimizeFunctionOnNextCall(C.prototype.foo);

  // Test the optimized function.
  r = o.foo();
  assertEquals("correct value", r);

  // Assert that the function was not deoptimized.
  assertOptimized(C.prototype.foo);

  // Change the property value.
  B.prototype.bar = "new value";
  r = o.foo();
  assertEquals("new value", r);

  // Assert that the function was deoptimized (dependency to the constant
  // value).
  // TODO(v8:11457) We don't support inlining JSLoadNamedFromSuper for
  // dictionary mode prototypes, yet. Therefore, if
  // v8_dict_property_const_tracking is enabled, the optimized code only
  // contains a call to the IC handler and doesn't get deopted.
  assertEquals(%IsDictPropertyConstTrackingEnabled(),
               isOptimized(C.prototype.foo));
})();

(function TestSuperpropertyAccessInlined() {
  class A {}
  A.prototype.bar = "wrong value: A.prototype.bar";

  class B extends A {}
  B.prototype.bar = "correct value";

  class C extends B {}

  let inline_this_is_being_interpreted;
  class D extends C {
    inline_this() {
      inline_this_is_being_interpreted = %IsBeingInterpreted();
      return super.bar;
    }
    foo() { return this.inline_this(); }
  }
  %PrepareFunctionForOptimization(D.prototype.inline_this);
  %PrepareFunctionForOptimization(D.prototype.foo);
  D.prototype.bar = "wrong value: D.prototype.bar";

  const o = new D();

  // Fill in the feedback.
  let r = o.foo();
  assertEquals("correct value", r);

  %OptimizeFunctionOnNextCall(D.prototype.foo);

  // Test the optimized function.
  r = o.foo();
  assertEquals("correct value", r);

  // Assert that the function was not deoptimized.
  assertOptimized(D.prototype.foo);

  // Assert that inline_this really got inlined.
  assertFalse(inline_this_is_being_interpreted);
})();

(function TestMegamorphicInlined() {
  class A {}
  A.prototype.bar = "wrong value: A.prototype.bar";

  class B extends A {}
  B.prototype.bar = "correct value";

  class C extends B {}

  let inline_this_is_being_interpreted;
  class D extends C {
    inline_this() {
      inline_this_is_being_interpreted = %IsBeingInterpreted();
      return super.bar;
    }
    foo() { return this.inline_this(); }
  }
  %PrepareFunctionForOptimization(D.prototype.inline_this);
  %PrepareFunctionForOptimization(D.prototype.foo);
  D.prototype.bar = "wrong value: D.prototype.bar";

  const o = new D();

  // Create objects which will act as the "home object's prototype" later.
  const prototypes = [{"a": 0}, {"b": 0}, {"c": 0}, {"d": 0}, {"e": 0},
  {"f": 0}, {"g": 0}, {"e": 0}];
  for (p of prototypes) {
    p.__proto__ = B.prototype;
  }

  // Fill in the feedback (megamorphic).
  for (p of prototypes) {
    D.prototype.__proto__ = p;
    const r = o.foo();
    assertEquals("correct value", r);
  }

  %OptimizeFunctionOnNextCall(D.prototype.foo);

  // Test the optimized function - don't change the home object's proto any
  // more.
  let r = o.foo();
  assertEquals("correct value", r);

  // Assert that the function was not deoptimized.
  assertOptimized(D.prototype.foo);

  // Assert that inline_this really got inlined.
  assertFalse(inline_this_is_being_interpreted);
})();

(function TestHomeObjectProtoIsGlobalThisGetterPropertyInlined() {
  class A {}

  let inline_this_is_being_interpreted;
  class B extends A {
    inline_this() {
      inline_this_is_being_interpreted = %IsBeingInterpreted();
      return super.bar;
    }
    foo() { return this.inline_this(); }
  }
  B.prototype.__proto__ = globalThis;
  Object.defineProperty(globalThis, "bar",
                        {get: function() { return this.this_value; }});
  %PrepareFunctionForOptimization(B.prototype.inline_this);
  %PrepareFunctionForOptimization(B.prototype.foo);

  let o = new B();
  o.this_value = "correct value";

  // Fill in the feedback.
  let r = o.foo();
  assertEquals("correct value", r);

  %OptimizeFunctionOnNextCall(B.prototype.foo);

  // Test the optimized function.
  r = o.foo();
  assertEquals("correct value", r);

  // Assert that the function was not deoptimized.
  assertOptimized(B.prototype.foo);

  // Assert that inline_this really got inlined.
  assertFalse(inline_this_is_being_interpreted);
})();
