From 1881cee6911df4dc4908118da6a76d08e78f682e Mon Sep 17 00:00:00 2001 From: "arv@chromium.org" Date: Tue, 28 Oct 2014 12:43:05 +0000 Subject: [PATCH] Classes: Add basic support for properties 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 | 68 +++++++++++ src/arm64/full-codegen-arm64.cc | 78 ++++++++++++- src/ast.h | 2 + src/full-codegen.cc | 4 +- src/full-codegen.h | 5 + src/ia32/full-codegen-ia32.cc | 61 ++++++++++ src/x64/full-codegen-x64.cc | 61 ++++++++++ test/mjsunit/harmony/classes.js | 246 ++++++++++++++++++++++++++++++++++++++-- 8 files changed, 507 insertions(+), 18 deletions(-) diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index 04e0675..76e6738 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -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) { diff --git a/src/arm64/full-codegen-arm64.cc b/src/arm64/full-codegen-arm64.cc index 7885984..a318b61 100644 --- a/src/arm64/full-codegen-arm64.cc +++ b/src/arm64/full-codegen-arm64.cc @@ -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 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); diff --git a/src/ast.h b/src/ast.h index ae7ec1a..714228f 100644 --- 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 friend class AstNodeFactory; diff --git a/src/full-codegen.cc b/src/full-codegen.cc index 9d31cd5..509d601 100644 --- a/src/full-codegen.cc +++ b/src/full-codegen.cc @@ -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()); } diff --git a/src/full-codegen.h b/src/full-codegen.h index 1aeb861..8c210ba 100644 --- a/src/full-codegen.h +++ b/src/full-codegen.h @@ -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, diff --git a/src/ia32/full-codegen-ia32.cc b/src/ia32/full-codegen-ia32.cc index a65a293..de08bd1 100644 --- a/src/ia32/full-codegen-ia32.cc +++ b/src/ia32/full-codegen-ia32.cc @@ -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) { diff --git a/src/x64/full-codegen-x64.cc b/src/x64/full-codegen-x64.cc index ba7ef08..fe06fc7 100644 --- a/src/x64/full-codegen-x64.cc +++ b/src/x64/full-codegen-x64.cc @@ -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) { diff --git a/test/mjsunit/harmony/classes.js b/test/mjsunit/harmony/classes.js index a83d483..3a22fd9 100644 --- a/test/mjsunit/harmony/classes.js +++ b/test/mjsunit/harmony/classes.js @@ -153,6 +153,240 @@ })(); + +(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 { @@ -165,15 +399,3 @@ 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()); -})(); -- 2.7.4