Classes: Add basic support for properties
authorarv@chromium.org <arv@chromium.org>
Tue, 28 Oct 2014 12:43:05 +0000 (12:43 +0000)
committerarv@chromium.org <arv@chromium.org>
Tue, 28 Oct 2014 12:43:49 +0000 (12:43 +0000)
This adds the properties to the prototype and the constructor.

BUG=v8:3330
LOG=Y
R=dslomov@chromium.org

Review URL: https://codereview.chromium.org/680993003

Cr-Commit-Position: refs/heads/master@{#24934}
git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@24934 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/arm/full-codegen-arm.cc
src/arm64/full-codegen-arm64.cc
src/ast.h
src/full-codegen.cc
src/full-codegen.h
src/ia32/full-codegen-ia32.cc
src/x64/full-codegen-x64.cc
test/mjsunit/harmony/classes.js

index 04e0675..76e6738 100644 (file)
@@ -2505,6 +2505,74 @@ void FullCodeGenerator::EmitInlineSmiBinaryOp(BinaryOperation* expr,
 }
 
 
+void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) {
+  // Constructor is in r0.
+  DCHECK(lit != NULL);
+  __ push(r0);
+
+  // No access check is needed here since the constructor is created by the
+  // class literal.
+  Register scratch = r1;
+  __ ldr(scratch,
+         FieldMemOperand(r0, JSFunction::kPrototypeOrInitialMapOffset));
+  __ push(scratch);
+
+  for (int i = 0; i < lit->properties()->length(); i++) {
+    ObjectLiteral::Property* property = lit->properties()->at(i);
+    Literal* key = property->key()->AsLiteral();
+    Expression* value = property->value();
+    DCHECK(key != NULL);
+
+    if (property->is_static()) {
+      __ ldr(scratch, MemOperand(sp, kPointerSize));  // constructor
+    } else {
+      __ ldr(scratch, MemOperand(sp, 0));  // prototype
+    }
+    __ push(scratch);
+    VisitForStackValue(key);
+
+    switch (property->kind()) {
+      case ObjectLiteral::Property::CONSTANT:
+      case ObjectLiteral::Property::MATERIALIZED_LITERAL:
+      case ObjectLiteral::Property::COMPUTED:
+      case ObjectLiteral::Property::PROTOTYPE:
+        VisitForStackValue(value);
+        __ mov(scratch, Operand(Smi::FromInt(NONE)));
+        __ push(scratch);
+        __ CallRuntime(Runtime::kDefineDataPropertyUnchecked, 4);
+        break;
+
+      case ObjectLiteral::Property::GETTER:
+        VisitForStackValue(value);
+        __ LoadRoot(scratch, Heap::kNullValueRootIndex);
+        __ push(scratch);
+        __ mov(scratch, Operand(Smi::FromInt(NONE)));
+        __ push(scratch);
+        __ CallRuntime(Runtime::kDefineAccessorPropertyUnchecked, 5);
+        break;
+
+      case ObjectLiteral::Property::SETTER:
+        __ LoadRoot(scratch, Heap::kNullValueRootIndex);
+        __ push(scratch);
+        VisitForStackValue(value);
+        __ mov(scratch, Operand(Smi::FromInt(NONE)));
+        __ push(scratch);
+        __ CallRuntime(Runtime::kDefineAccessorPropertyUnchecked, 5);
+        break;
+
+      default:
+        UNREACHABLE();
+    }
+  }
+
+  // prototype
+  __ CallRuntime(Runtime::kToFastProperties, 1);
+
+  // constructor
+  __ CallRuntime(Runtime::kToFastProperties, 1);
+}
+
+
 void FullCodeGenerator::EmitBinaryOp(BinaryOperation* expr,
                                      Token::Value op,
                                      OverwriteMode mode) {
index 7885984..a318b61 100644 (file)
@@ -300,7 +300,8 @@ void FullCodeGenerator::Generate() {
       VisitDeclarations(scope()->declarations());
     }
 
-    { Comment cmnt(masm_, "[ Stack check");
+    {
+      Comment cmnt(masm_, "[ Stack check");
       PrepareForBailoutForId(BailoutId::Declarations(), NO_REGISTERS);
       Label ok;
       DCHECK(jssp.Is(__ StackPointer()));
@@ -312,7 +313,8 @@ void FullCodeGenerator::Generate() {
       __ Bind(&ok);
     }
 
-    { Comment cmnt(masm_, "[ Body");
+    {
+      Comment cmnt(masm_, "[ Body");
       DCHECK(loop_depth() == 0);
       VisitStatements(function()->body());
       DCHECK(loop_depth() == 0);
@@ -2040,7 +2042,7 @@ void FullCodeGenerator::EmitNamedSuperPropertyLoad(Property* prop) {
 
 void FullCodeGenerator::EmitKeyedPropertyLoad(Property* prop) {
   SetSourcePosition(prop->position());
-  // Call keyed load IC. It has arguments key and receiver in r0 and r1.
+  // Call keyed load IC. It has arguments key and receiver in x0 and x1.
   Handle<Code> ic = CodeFactory::KeyedLoadIC(isolate()).code();
   if (FLAG_vector_ics) {
     __ Mov(VectorLoadICDescriptor::SlotRegister(),
@@ -2175,6 +2177,74 @@ void FullCodeGenerator::EmitBinaryOp(BinaryOperation* expr,
 }
 
 
+void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) {
+  // Constructor is in x0.
+  DCHECK(lit != NULL);
+  __ push(x0);
+
+  // No access check is needed here since the constructor is created by the
+  // class literal.
+  Register scratch = x1;
+  __ Ldr(scratch,
+         FieldMemOperand(x0, JSFunction::kPrototypeOrInitialMapOffset));
+  __ Push(scratch);
+
+  for (int i = 0; i < lit->properties()->length(); i++) {
+    ObjectLiteral::Property* property = lit->properties()->at(i);
+    Literal* key = property->key()->AsLiteral();
+    Expression* value = property->value();
+    DCHECK(key != NULL);
+
+    if (property->is_static()) {
+      __ Peek(scratch, kPointerSize);  // constructor
+    } else {
+      __ Peek(scratch, 0);  // prototype
+    }
+    __ Push(scratch);
+    VisitForStackValue(key);
+
+    switch (property->kind()) {
+      case ObjectLiteral::Property::CONSTANT:
+      case ObjectLiteral::Property::MATERIALIZED_LITERAL:
+      case ObjectLiteral::Property::COMPUTED:
+      case ObjectLiteral::Property::PROTOTYPE:
+        VisitForStackValue(value);
+        __ Mov(scratch, Smi::FromInt(NONE));
+        __ Push(scratch);
+        __ CallRuntime(Runtime::kDefineDataPropertyUnchecked, 4);
+        break;
+
+      case ObjectLiteral::Property::GETTER:
+        VisitForStackValue(value);
+        __ LoadRoot(scratch, Heap::kNullValueRootIndex);
+        __ push(scratch);
+        __ Mov(scratch, Smi::FromInt(NONE));
+        __ Push(scratch);
+        __ CallRuntime(Runtime::kDefineAccessorPropertyUnchecked, 5);
+        break;
+
+      case ObjectLiteral::Property::SETTER:
+        __ LoadRoot(scratch, Heap::kNullValueRootIndex);
+        __ push(scratch);
+        VisitForStackValue(value);
+        __ Mov(scratch, Smi::FromInt(NONE));
+        __ Push(scratch);
+        __ CallRuntime(Runtime::kDefineAccessorPropertyUnchecked, 5);
+        break;
+
+      default:
+        UNREACHABLE();
+    }
+  }
+
+  // prototype
+  __ CallRuntime(Runtime::kToFastProperties, 1);
+
+  // constructor
+  __ CallRuntime(Runtime::kToFastProperties, 1);
+}
+
+
 void FullCodeGenerator::EmitAssignment(Expression* expr) {
   DCHECK(expr->IsValidReferenceExpression());
 
@@ -4859,7 +4929,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
 
   // The value stays in x0, and is ultimately read by the resumed generator, as
   // if CallRuntime(Runtime::kSuspendJSGeneratorObject) returned it. Or it
-  // is read to throw the value when the resumed generator is already closed. r1
+  // is read to throw the value when the resumed generator is already closed. x1
   // will hold the generator object until the activation has been resumed.
   VisitForStackValue(generator);
   VisitForAccumulatorValue(value);
index ae7ec1a..714228f 100644 (file)
--- a/src/ast.h
+++ b/src/ast.h
@@ -1512,6 +1512,8 @@ class ObjectLiteralProperty FINAL : public ZoneObject {
   void set_emit_store(bool emit_store);
   bool emit_store();
 
+  bool is_static() const { return is_static_; }
+
  protected:
   template<class> friend class AstNodeFactory;
 
index 9d31cd5..509d601 100644 (file)
@@ -1585,9 +1585,9 @@ void FullCodeGenerator::VisitClassLiteral(ClassLiteral* lit) {
   __ Push(Smi::FromInt(lit->start_position()));
   __ Push(Smi::FromInt(lit->end_position()));
 
-  // TODO(arv): Process methods
-
   __ CallRuntime(Runtime::kDefineClass, 6);
+  EmitClassDefineProperties(lit);
+
   context()->Plug(result_register());
 }
 
index 1aeb861..8c210ba 100644 (file)
@@ -563,6 +563,11 @@ class FullCodeGenerator: public AstVisitor {
   // The receiver and the key is left on the stack by the IC.
   void EmitKeyedPropertyLoad(Property* expr);
 
+  // Adds the properties to the class (function) object and to its prototype.
+  // Expects the class (function) in the accumulator. The class (function) is
+  // in the accumulator after installing all the properties.
+  void EmitClassDefineProperties(ClassLiteral* lit);
+
   // Apply the compound assignment operator. Expects the left operand on top
   // of the stack and the right one in the accumulator.
   void EmitBinaryOp(BinaryOperation* expr,
index a65a293..de08bd1 100644 (file)
@@ -2421,6 +2421,67 @@ void FullCodeGenerator::EmitInlineSmiBinaryOp(BinaryOperation* expr,
 }
 
 
+void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) {
+  // Constructor is in eax.
+  DCHECK(lit != NULL);
+  __ push(eax);
+
+  // No access check is needed here since the constructor is created by the
+  // class literal.
+  Register scratch = ebx;
+  __ mov(scratch, FieldOperand(eax, JSFunction::kPrototypeOrInitialMapOffset));
+  __ Push(scratch);
+
+  for (int i = 0; i < lit->properties()->length(); i++) {
+    ObjectLiteral::Property* property = lit->properties()->at(i);
+    Literal* key = property->key()->AsLiteral();
+    Expression* value = property->value();
+    DCHECK(key != NULL);
+
+    if (property->is_static()) {
+      __ push(Operand(esp, kPointerSize));  // constructor
+    } else {
+      __ push(Operand(esp, 0));  // prototype
+    }
+    VisitForStackValue(key);
+
+    switch (property->kind()) {
+      case ObjectLiteral::Property::CONSTANT:
+      case ObjectLiteral::Property::MATERIALIZED_LITERAL:
+      case ObjectLiteral::Property::COMPUTED:
+      case ObjectLiteral::Property::PROTOTYPE:
+        VisitForStackValue(value);
+        __ push(Immediate(Smi::FromInt(NONE)));
+        __ CallRuntime(Runtime::kDefineDataPropertyUnchecked, 4);
+        break;
+
+      case ObjectLiteral::Property::GETTER:
+        VisitForStackValue(value);
+        __ push(Immediate(isolate()->factory()->null_value()));
+        __ push(Immediate(Smi::FromInt(NONE)));
+        __ CallRuntime(Runtime::kDefineAccessorPropertyUnchecked, 5);
+        break;
+
+      case ObjectLiteral::Property::SETTER:
+        __ push(Immediate(isolate()->factory()->null_value()));
+        VisitForStackValue(value);
+        __ push(Immediate(Smi::FromInt(NONE)));
+        __ CallRuntime(Runtime::kDefineAccessorPropertyUnchecked, 5);
+        break;
+
+      default:
+        UNREACHABLE();
+    }
+  }
+
+  // prototype
+  __ CallRuntime(Runtime::kToFastProperties, 1);
+
+  // constructor
+  __ CallRuntime(Runtime::kToFastProperties, 1);
+}
+
+
 void FullCodeGenerator::EmitBinaryOp(BinaryOperation* expr,
                                      Token::Value op,
                                      OverwriteMode mode) {
index ba7ef08..fe06fc7 100644 (file)
@@ -2420,6 +2420,67 @@ void FullCodeGenerator::EmitInlineSmiBinaryOp(BinaryOperation* expr,
 }
 
 
+void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) {
+  // Constructor is in rax.
+  DCHECK(lit != NULL);
+  __ Push(rax);
+
+  // No access check is needed here since the constructor is created by the
+  // class literal.
+  Register scratch = rbx;
+  __ movp(scratch, FieldOperand(rax, JSFunction::kPrototypeOrInitialMapOffset));
+  __ Push(scratch);
+
+  for (int i = 0; i < lit->properties()->length(); i++) {
+    ObjectLiteral::Property* property = lit->properties()->at(i);
+    Literal* key = property->key()->AsLiteral();
+    Expression* value = property->value();
+    DCHECK(key != NULL);
+
+    if (property->is_static()) {
+      __ Push(Operand(rsp, kPointerSize));  // constructor
+    } else {
+      __ Push(Operand(rsp, 0));  // prototype
+    }
+    VisitForStackValue(key);
+
+    switch (property->kind()) {
+      case ObjectLiteral::Property::CONSTANT:
+      case ObjectLiteral::Property::MATERIALIZED_LITERAL:
+      case ObjectLiteral::Property::COMPUTED:
+      case ObjectLiteral::Property::PROTOTYPE:
+        VisitForStackValue(value);
+        __ Push(Smi::FromInt(NONE));
+        __ CallRuntime(Runtime::kDefineDataPropertyUnchecked, 4);
+        break;
+
+      case ObjectLiteral::Property::GETTER:
+        VisitForStackValue(value);
+        __ Push(isolate()->factory()->null_value());
+        __ Push(Smi::FromInt(NONE));
+        __ CallRuntime(Runtime::kDefineAccessorPropertyUnchecked, 5);
+        break;
+
+      case ObjectLiteral::Property::SETTER:
+        __ Push(isolate()->factory()->null_value());
+        VisitForStackValue(value);
+        __ Push(Smi::FromInt(NONE));
+        __ CallRuntime(Runtime::kDefineAccessorPropertyUnchecked, 5);
+        break;
+
+      default:
+        UNREACHABLE();
+    }
+  }
+
+  // prototype
+  __ CallRuntime(Runtime::kToFastProperties, 1);
+
+  // constructor
+  __ CallRuntime(Runtime::kToFastProperties, 1);
+}
+
+
 void FullCodeGenerator::EmitBinaryOp(BinaryOperation* expr,
                                      Token::Value op,
                                      OverwriteMode mode) {
index a83d483..3a22fd9 100644 (file)
 
 })();
 
+
+(function TestToString() {
+  class C {}
+  assertEquals('class C {}', C.toString());
+
+  class D { constructor() { 42; } }
+  assertEquals('class D { constructor() { 42; } }', D.toString());
+
+  class E { x() { 42; } }
+  assertEquals('class E { x() { 42; } }', E.toString());
+})();
+
+
+function assertMethodDescriptor(object, name) {
+  var descr = Object.getOwnPropertyDescriptor(object, name);
+  assertTrue(descr.configurable);
+  assertTrue(descr.enumerable);
+  assertTrue(descr.writable);
+  assertEquals('function', typeof descr.value);
+}
+
+function assertGetterDescriptor(object, name) {
+  var descr = Object.getOwnPropertyDescriptor(object, name);
+  assertTrue(descr.configurable);
+  assertTrue(descr.enumerable);
+  assertEquals('function', typeof descr.get);
+  assertEquals(undefined, descr.set);
+}
+
+
+function assertSetterDescriptor(object, name) {
+  var descr = Object.getOwnPropertyDescriptor(object, name);
+  assertTrue(descr.configurable);
+  assertTrue(descr.enumerable);
+  assertEquals(undefined, descr.get);
+  assertEquals('function', typeof descr.set);
+}
+
+
+function assertAccessorDescriptor(object, name) {
+  var descr = Object.getOwnPropertyDescriptor(object, name);
+  assertTrue(descr.configurable);
+  assertTrue(descr.enumerable);
+  assertEquals('function', typeof descr.get);
+  assertEquals('function', typeof descr.set);
+}
+
+
+(function TestMethods() {
+  class C {
+    method() { return 1; }
+    static staticMethod() { return 2; }
+    method2() { return 3; }
+    static staticMethod2() { return 4; }
+  }
+
+  assertMethodDescriptor(C.prototype, 'method');
+  assertMethodDescriptor(C.prototype, 'method2');
+  assertMethodDescriptor(C, 'staticMethod');
+  assertMethodDescriptor(C, 'staticMethod2');
+
+  assertEquals(1, new C().method());
+  assertEquals(2, C.staticMethod());
+  assertEquals(3, new C().method2());
+  assertEquals(4, C.staticMethod2());
+})();
+
+
+(function TestGetters() {
+  class C {
+    get x() { return 1; }
+    static get staticX() { return 2; }
+    get y() { return 3; }
+    static get staticY() { return 4; }
+  }
+
+  assertGetterDescriptor(C.prototype, 'x');
+  assertGetterDescriptor(C.prototype, 'y');
+  assertGetterDescriptor(C, 'staticX');
+  assertGetterDescriptor(C, 'staticY');
+
+  assertEquals(1, new C().x);
+  assertEquals(2, C.staticX);
+  assertEquals(3, new C().y);
+  assertEquals(4, C.staticY);
+})();
+
+
+
+(function TestSetters() {
+  var x, staticX, y, staticY;
+  class C {
+    set x(v) { x = v; }
+    static set staticX(v) { staticX = v; }
+    set y(v) { y = v; }
+    static set staticY(v) { staticY = v; }
+  }
+
+  assertSetterDescriptor(C.prototype, 'x');
+  assertSetterDescriptor(C.prototype, 'y');
+  assertSetterDescriptor(C, 'staticX');
+  assertSetterDescriptor(C, 'staticY');
+
+  assertEquals(1, new C().x = 1);
+  assertEquals(1, x);
+  assertEquals(2, C.staticX = 2);
+  assertEquals(2, staticX);
+  assertEquals(3, new C().y = 3);
+  assertEquals(3, y);
+  assertEquals(4, C.staticY = 4);
+  assertEquals(4, staticY);
+})();
+
+
+(function TestSideEffectsInPropertyDefine() {
+  function B() {}
+  B.prototype = {
+    constructor: B,
+    set m(v) {
+      throw Error();
+    }
+  };
+
+  class C extends B {
+    m() { return 1; }
+  }
+
+  assertEquals(1, new C().m());
+})();
+
+
+(function TestAccessors() {
+  class C {
+    constructor(x) {
+      this._x = x;
+    }
+
+    get x() { return this._x; }
+    set x(v) { this._x = v; }
+
+    static get staticX() { return this._x; }
+    static set staticX(v) { this._x = v; }
+  }
+
+  assertAccessorDescriptor(C.prototype, 'x');
+  assertAccessorDescriptor(C, 'staticX');
+
+  var c = new C(1);
+  c._x = 1;
+  assertEquals(1, c.x);
+  c.x = 2;
+  assertEquals(2, c._x);
+
+  C._x = 3;
+  assertEquals(3, C.staticX);
+  C._x = 4;
+  assertEquals(4, C.staticX );
+})();
+
+
+(function TestProto() {
+  class C {
+    __proto__() { return 1; }
+  }
+  assertMethodDescriptor(C.prototype, '__proto__');
+  assertEquals(1, new C().__proto__());
+})();
+
+
+(function TestProtoStatic() {
+  class C {
+    static __proto__() { return 1; }
+  }
+  assertMethodDescriptor(C, '__proto__');
+  assertEquals(1, C.__proto__());
+})();
+
+
+(function TestProtoAccessor() {
+  class C {
+    get __proto__() { return this._p; }
+    set __proto__(v) { this._p = v; }
+  }
+  assertAccessorDescriptor(C.prototype, '__proto__');
+  var c = new C();
+  c._p = 1;
+  assertEquals(1, c.__proto__);
+  c.__proto__ = 2;
+  assertEquals(2, c.__proto__);
+})();
+
+
+(function TestStaticProtoAccessor() {
+  class C {
+    static get __proto__() { return this._p; }
+    static set __proto__(v) { this._p = v; }
+  }
+  assertAccessorDescriptor(C, '__proto__');
+  C._p = 1;
+  assertEquals(1, C.__proto__);
+  C.__proto__ = 2;
+  assertEquals(2, C.__proto__);
+})();
+
+
+(function TestSettersOnProto() {
+  function Base() {}
+  Base.prototype = {
+    set constructor(_) {
+      assertUnreachable();
+    },
+    set m(_) {
+      assertUnreachable();
+    }
+  };
+  Object.defineProperty(Base, 'staticM', {
+    set: function() {
+      assertUnreachable();
+    }
+  });
+
+  class C extends Base {
+    m() {
+      return 1;
+    }
+    static staticM() {
+      return 2;
+    }
+  }
+
+  assertEquals(1, new C().m());
+  assertEquals(2, C.staticM());
+})();
+
 /* TODO(arv): Implement
 (function TestNameBindingInConstructor() {
   class C {
   new C();
 })();
 */
-
-
-(function TestToString() {
-  class C {}
-  assertEquals('class C {}', C.toString());
-
-  class D { constructor() { 42; } }
-  assertEquals('class D { constructor() { 42; } }', D.toString());
-
-  class E { x() { 42; } }
-  assertEquals('class E { x() { 42; } }', E.toString());
-})();