Generate specialized constructor code for constructing simple objects.
authorsgjesse@chromium.org <sgjesse@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 25 Aug 2009 12:23:58 +0000 (12:23 +0000)
committersgjesse@chromium.org <sgjesse@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 25 Aug 2009 12:23:58 +0000 (12:23 +0000)
For objects which only have simple assignments of the form this.x = ...; a specialized constructor stub is now generated. This generated code allocates the object and fills in the initial properties directly. If this fails for some reason code continues in the generic constructor stub which in turn might pass control to the runtime system.

Added counter to see how many objects are constructed using a specialized stub.

The specialized stub is only implemented for ia32 architecture in this change. For x64 and ARM the generic construct stub is used.
Review URL: http://codereview.chromium.org/174392

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@2753 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

15 files changed:
src/arm/stub-cache-arm.cc
src/compiler.cc
src/debug-delay.js
src/ia32/builtins-ia32.cc
src/ia32/stub-cache-ia32.cc
src/objects.cc
src/objects.h
src/runtime.cc
src/runtime.h
src/stub-cache.cc
src/stub-cache.h
src/v8-counters.h
src/x64/stub-cache-x64.cc
test/mjsunit/debug-stepin-constructor.js
test/mjsunit/simple-constructor.js

index 03e0779..1581428 100644 (file)
@@ -1342,6 +1342,18 @@ Object* KeyedStoreStubCompiler::CompileStoreField(JSObject* object,
 }
 
 
+Object* ConstructStubCompiler::CompileConstructStub(
+    SharedFunctionInfo* shared) {
+  // Not implemented yet - just jump to generic stub.
+  Code* code = Builtins::builtin(Builtins::JSConstructStubGeneric);
+  Handle<Code> generic_construct_stub(code);
+  __ Jump(generic_construct_stub, RelocInfo::CODE_TARGET);
+
+  // Return the generated code.
+  return GetCode();
+}
+
+
 #undef __
 
 } }  // namespace v8::internal
index feff492..15f6479 100644 (file)
@@ -425,6 +425,13 @@ bool Compiler::CompileLazy(Handle<SharedFunctionInfo> shared,
   // Set the expected number of properties for instances.
   SetExpectedNofPropertiesFromEstimate(shared, lit->expected_property_count());
 
+  // Set the optimication hints after performing lazy compilation, as these are
+  // not set when the function is set up as a lazily compiled function.
+  shared->SetThisPropertyAssignmentsInfo(
+      lit->has_only_this_property_assignments(),
+      lit->has_only_simple_this_property_assignments(),
+      *lit->this_property_assignments());
+
   // Check the function has compiled code.
   ASSERT(shared->is_compiled());
   return true;
index 4f60851..ce70c75 100644 (file)
@@ -466,9 +466,14 @@ Debug.source = function(f) {
   return %FunctionGetSourceCode(f);
 };
 
-Debug.assembler = function(f) {
+Debug.disassemble = function(f) {
   if (!IS_FUNCTION(f)) throw new Error('Parameters have wrong types.');
-  return %FunctionGetAssemblerCode(f);
+  return %DebugDisassembleFunction(f);
+};
+
+Debug.disassembleConstructor = function(f) {
+  if (!IS_FUNCTION(f)) throw new Error('Parameters have wrong types.');
+  return %DebugDisassembleConstructor(f);
 };
 
 Debug.sourcePosition = function(f) {
index 6de9de6..e679d85 100644 (file)
@@ -132,7 +132,7 @@ void Builtins::Generate_JSConstructStubGeneric(MacroAssembler* masm) {
     // Make sure that the maximum heap object size will never cause us
     // problem here, because it is always greater than the maximum
     // instance size that can be represented in a byte.
-    ASSERT(Heap::MaxObjectSizeInPagedSpace() >= (1 << kBitsPerByte));
+    ASSERT(Heap::MaxObjectSizeInPagedSpace() >= JSObject::kMaxInstanceSize);
     ExternalReference new_space_allocation_top =
         ExternalReference::new_space_allocation_top_address();
     __ mov(ebx, Operand::StaticVariable(new_space_allocation_top));
index a626377..310bff6 100644 (file)
@@ -1740,6 +1740,146 @@ Object* KeyedLoadStubCompiler::CompileLoadFunctionPrototype(String* name) {
 }
 
 
+// Specialized stub for constructing objects from functions which only have only
+// simple assignments of the form this.x = ...; in their body.
+Object* ConstructStubCompiler::CompileConstructStub(
+    SharedFunctionInfo* shared) {
+  // ----------- S t a t e -------------
+  //  -- eax : argc
+  //  -- edi : constructor
+  //  -- esp[0] : return address
+  //  -- esp[4] : last argument
+  // -----------------------------------
+  Label generic_stub_call;
+#ifdef ENABLE_DEBUGGER_SUPPORT
+  // Check to see whether there are any break points in the function code. If
+  // there are jump to the generic constructor stub which calls the actual
+  // code for the function thereby hitting the break points.
+  __ mov(ebx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
+  __ mov(ebx, FieldOperand(ebx, SharedFunctionInfo::kDebugInfoOffset));
+  __ cmp(ebx, Factory::undefined_value());
+  __ j(not_equal, &generic_stub_call, not_taken);
+#endif
+
+  // Load the initial map and verify that it is in fact a map.
+  __ mov(ebx, FieldOperand(edi, JSFunction::kPrototypeOrInitialMapOffset));
+  // Will both indicate a NULL and a Smi.
+  __ test(ebx, Immediate(kSmiTagMask));
+  __ j(zero, &generic_stub_call);
+  __ CmpObjectType(ebx, MAP_TYPE, ecx);
+  __ j(not_equal, &generic_stub_call);
+
+#ifdef DEBUG
+  // Cannot construct functions this way.
+  // edi: constructor
+  // ebx: initial map
+  __ CmpInstanceType(ebx, JS_FUNCTION_TYPE);
+  __ Assert(not_equal, "Function constructed by construct stub.");
+#endif
+
+  // Now allocate the JSObject on the heap by moving the new space allocation
+  // top forward.
+  // edi: constructor
+  // ebx: initial map
+  __ movzx_b(ecx, FieldOperand(ebx, Map::kInstanceSizeOffset));
+  __ shl(ecx, kPointerSizeLog2);
+  // Make sure that the maximum heap object size will never cause us
+  // problems here.
+  ASSERT(Heap::MaxObjectSizeInPagedSpace() >= JSObject::kMaxInstanceSize);
+  ExternalReference new_space_allocation_top =
+      ExternalReference::new_space_allocation_top_address();
+  __ mov(edx, Operand::StaticVariable(new_space_allocation_top));
+  __ add(ecx, Operand(edx));  // Calculate new top.
+  ExternalReference new_space_allocation_limit =
+      ExternalReference::new_space_allocation_limit_address();
+  __ cmp(ecx, Operand::StaticVariable(new_space_allocation_limit));
+  __ j(above_equal, &generic_stub_call);
+
+  // Update new space top.
+  __ mov(Operand::StaticVariable(new_space_allocation_top), ecx);
+
+  // Allocated the JSObject, now initialize the fields and add the heap tag.
+  // ebx: initial map
+  // edx: JSObject
+  __ mov(Operand(edx, JSObject::kMapOffset), ebx);
+  __ mov(ebx, Factory::empty_fixed_array());
+  __ mov(Operand(edx, JSObject::kPropertiesOffset), ebx);
+  __ mov(Operand(edx, JSObject::kElementsOffset), ebx);
+  __ or_(Operand(edx), Immediate(kHeapObjectTag));
+
+  // Push the allocated object to the stack. This is the object that will be
+  // returned.
+  __ push(edx);
+
+  // eax: argc
+  // edx: JSObject
+  // Load the address of the first in-object property into edx.
+  __ lea(edx, Operand(edx, JSObject::kHeaderSize));
+  __ xor_(Operand(edx), Immediate(kHeapObjectTag));  // Clear heap object tag.
+  // Calculate the location of the first argument. The stack contains the
+  // allocated object and the return address on top of the argc arguments.
+  __ lea(ecx, Operand(esp, eax, times_4, 1 * kPointerSize));
+
+  // Use edi for holding undefined which is used in several places below.
+  __ mov(edi, Factory::undefined_value());
+
+  // eax: argc
+  // ecx: first argument
+  // edx: first in-object property of the JSObject
+  // edi: undefined
+  // Fill the initialized properties with a constant value or a passed argument
+  // depending on the this.x = ...; assignment in the function.
+  for (int i = 0; i < shared->this_property_assignments_count(); i++) {
+    if (shared->IsThisPropertyAssignmentArgument(i)) {
+      Label not_passed;
+      // Set the property to undefined.
+      __ mov(Operand(edx, i * kPointerSize), edi);
+      // Check if the argument assigned to the property is actually passed.
+      int arg_number = shared->GetThisPropertyAssignmentArgument(i);
+      __ cmp(eax, arg_number);
+      __ j(below_equal, &not_passed);
+      // Argument passed - find it on the stack.
+      __ mov(ebx, Operand(ecx, arg_number * -kPointerSize));
+      __ mov(Operand(edx, i * kPointerSize), ebx);
+      __ bind(&not_passed);
+    } else {
+      // Set the property to the constant value.
+      Handle<Object> constant(shared->GetThisPropertyAssignmentConstant(i));
+      __ mov(Operand(edx, i * kPointerSize), Immediate(constant));
+    }
+  }
+
+  // Fill the unused in-object property fields with undefined.
+  for (int i = shared->this_property_assignments_count();
+       i < shared->CalculateInObjectProperties();
+       i++) {
+    __ mov(Operand(edx, i * kPointerSize), edi);
+  }
+
+  // Move argc to ebx and retreive the JSObject to return.
+  __ mov(ebx, eax);
+  __ pop(eax);
+
+  // Remove caller arguments from the stack and return.
+  __ pop(ecx);
+  __ lea(esp, Operand(esp, ebx, times_4, 1 * kPointerSize));  // 1 ~ receiver
+  __ push(ecx);
+  __ IncrementCounter(&Counters::constructed_objects, 1);
+  __ IncrementCounter(&Counters::constructed_objects_stub, 1);
+  __ ret(0);
+
+  // Jump to the generic stub in case the specialized code cannot handle the
+  // construction.
+  __ bind(&generic_stub_call);
+  Code* code = Builtins::builtin(Builtins::JSConstructStubGeneric);
+  Handle<Code> generic_construct_stub(code);
+  __ jmp(generic_construct_stub, RelocInfo::CODE_TARGET);
+
+  // Return the generated code.
+  return GetCode();
+}
+
+
 #undef __
 
 } }  // namespace v8::internal
index e4a3a67..cf09d88 100644 (file)
@@ -4800,7 +4800,6 @@ void SharedFunctionInfo::SetThisPropertyAssignmentsInfo(
     bool only_this_property_assignments,
     bool only_simple_this_property_assignments,
     FixedArray* assignments) {
-  ASSERT(this_property_assignments()->IsUndefined());
   set_compiler_hints(BooleanBit::set(compiler_hints(),
                                      kHasOnlyThisPropertyAssignments,
                                      only_this_property_assignments));
@@ -4812,6 +4811,18 @@ void SharedFunctionInfo::SetThisPropertyAssignmentsInfo(
 }
 
 
+void SharedFunctionInfo::ClearThisPropertyAssignmentsInfo() {
+  set_compiler_hints(BooleanBit::set(compiler_hints(),
+                                     kHasOnlyThisPropertyAssignments,
+                                     false));
+  set_compiler_hints(BooleanBit::set(compiler_hints(),
+                                     kHasOnlySimpleThisPropertyAssignments,
+                                     false));
+  set_this_property_assignments(Heap::undefined_value());
+  set_this_property_assignments_count(0);
+}
+
+
 String* SharedFunctionInfo::GetThisPropertyAssignmentName(int index) {
   Object* obj = this_property_assignments();
   ASSERT(obj->IsFixedArray());
@@ -4822,6 +4833,32 @@ String* SharedFunctionInfo::GetThisPropertyAssignmentName(int index) {
 }
 
 
+bool SharedFunctionInfo::IsThisPropertyAssignmentArgument(int index) {
+  Object* obj = this_property_assignments();
+  ASSERT(obj->IsFixedArray());
+  ASSERT(index < this_property_assignments_count());
+  obj = FixedArray::cast(obj)->get(index * 3 + 1);
+  return Smi::cast(obj)->value() != -1;
+}
+
+
+int SharedFunctionInfo::GetThisPropertyAssignmentArgument(int index) {
+  ASSERT(IsThisPropertyAssignmentArgument(index));
+  Object* obj =
+      FixedArray::cast(this_property_assignments())->get(index * 3 + 1);
+  return Smi::cast(obj)->value();
+}
+
+
+Object* SharedFunctionInfo::GetThisPropertyAssignmentConstant(int index) {
+  ASSERT(!IsThisPropertyAssignmentArgument(index));
+  Object* obj =
+      FixedArray::cast(this_property_assignments())->get(index * 3 + 2);
+  return obj;
+}
+
+
+
 // Support function for printing the source code to a StringStream
 // without any allocation in the heap.
 void SharedFunctionInfo::SourceCodePrint(StringStream* accumulator,
index 763752b..b354220 100644 (file)
@@ -3108,6 +3108,9 @@ class SharedFunctionInfo: public HeapObject {
       bool has_only_simple_this_property_assignments,
       FixedArray* this_property_assignments);
 
+  // Clear information on assignments of the form this.x = ...;
+  void ClearThisPropertyAssignmentsInfo();
+
   // Indicate that this function only consists of assignments of the form
   // this.x = ...;.
   inline bool has_only_this_property_assignments();
@@ -3122,6 +3125,9 @@ class SharedFunctionInfo: public HeapObject {
   inline int this_property_assignments_count();
   inline void set_this_property_assignments_count(int value);
   String* GetThisPropertyAssignmentName(int index);
+  bool IsThisPropertyAssignmentArgument(int index);
+  int GetThisPropertyAssignmentArgument(int index);
+  Object* GetThisPropertyAssignmentConstant(int index);
 
   // [source code]: Source code for the function.
   bool HasSourceCode();
index 845ac63..213d9a3 100644 (file)
@@ -45,6 +45,7 @@
 #include "v8threads.h"
 #include "smart-pointer.h"
 #include "parser.h"
+#include "stub-cache.h"
 
 namespace v8 {
 namespace internal {
@@ -1235,6 +1236,9 @@ static Object* Runtime_SetCode(Arguments args) {
     // Array, and Object, and some web code
     // doesn't like seeing source code for constructors.
     target->shared()->set_script(Heap::undefined_value());
+    // Clear the optimization hints related to the compiled code as these are no
+    // longer valid when the code is overwritten.
+    target->shared()->ClearThisPropertyAssignmentsInfo();
     context = Handle<Context>(fun->context());
 
     // Make sure we get a fresh copy of the literal vector to avoid
@@ -4326,11 +4330,21 @@ static Object* Runtime_NewClosure(Arguments args) {
 }
 
 
-static Handle<Code> ComputeConstructStub(Handle<Map> map) {
+static Code* ComputeConstructStub(Handle<SharedFunctionInfo> shared) {
   // TODO(385): Change this to create a construct stub specialized for
   // the given map to make allocation of simple objects - and maybe
   // arrays - much faster.
-  return Handle<Code>(Builtins::builtin(Builtins::JSConstructStubGeneric));
+  if (FLAG_inline_new
+      && shared->has_only_simple_this_property_assignments()) {
+    ConstructStubCompiler compiler;
+    Object* code = compiler.CompileConstructStub(*shared);
+    if (code->IsFailure()) {
+      return Builtins::builtin(Builtins::JSConstructStubGeneric);
+    }
+    return Code::cast(code);
+  }
+
+  return Builtins::builtin(Builtins::JSConstructStubGeneric);
 }
 
 
@@ -4373,15 +4387,25 @@ static Object* Runtime_NewObject(Arguments args) {
     }
   }
 
+  // The function should be compiled for the optimization hints to be available.
+  if (!function->shared()->is_compiled()) {
+    CompileLazyShared(Handle<SharedFunctionInfo>(function->shared()),
+                                                 CLEAR_EXCEPTION,
+                                                 0);
+  }
+
   bool first_allocation = !function->has_initial_map();
   Handle<JSObject> result = Factory::NewJSObject(function);
   if (first_allocation) {
     Handle<Map> map = Handle<Map>(function->initial_map());
-    Handle<Code> stub = ComputeConstructStub(map);
+    Handle<Code> stub = Handle<Code>(
+        ComputeConstructStub(Handle<SharedFunctionInfo>(function->shared())));
     function->shared()->set_construct_stub(*stub);
   }
+
   Counters::constructed_objects.Increment();
   Counters::constructed_objects_runtime.Increment();
+
   return *result;
 }
 
@@ -7386,7 +7410,7 @@ static Object* Runtime_SystemBreak(Arguments args) {
 }
 
 
-static Object* Runtime_FunctionGetAssemblerCode(Arguments args) {
+static Object* Runtime_DebugDisassembleFunction(Arguments args) {
 #ifdef DEBUG
   HandleScope scope;
   ASSERT(args.length() == 1);
@@ -7401,6 +7425,21 @@ static Object* Runtime_FunctionGetAssemblerCode(Arguments args) {
 }
 
 
+static Object* Runtime_DebugDisassembleConstructor(Arguments args) {
+#ifdef DEBUG
+  HandleScope scope;
+  ASSERT(args.length() == 1);
+  // Get the function and make sure it is compiled.
+  CONVERT_ARG_CHECKED(JSFunction, func, 0);
+  if (!func->is_compiled() && !CompileLazy(func, KEEP_EXCEPTION)) {
+    return Failure::Exception();
+  }
+  func->shared()->construct_stub()->PrintLn();
+#endif  // DEBUG
+  return Heap::undefined_value();
+}
+
+
 static Object* Runtime_FunctionGetInferredName(Arguments args) {
   NoHandleAllocation ha;
   ASSERT(args.length() == 1);
index d47ca18..1be677a 100644 (file)
@@ -303,7 +303,8 @@ namespace internal {
   F(DebugConstructedBy, 2) \
   F(DebugGetPrototype, 1) \
   F(SystemBreak, 0) \
-  F(FunctionGetAssemblerCode, 1) \
+  F(DebugDisassembleFunction, 1) \
+  F(DebugDisassembleConstructor, 1) \
   F(FunctionGetInferredName, 1)
 #else
 #define RUNTIME_FUNCTION_LIST_DEBUGGER_SUPPORT(F)
index b25f5b4..a719f29 100644 (file)
@@ -1097,4 +1097,11 @@ Object* CallStubCompiler::GetCode(PropertyType type, String* name) {
 }
 
 
+Object* ConstructStubCompiler::GetCode() {
+  Code::Flags flags = Code::ComputeFlags(Code::STUB);
+  return GetCodeWithFlags(flags, "ConstructStub");
+}
+
+
+
 } }  // namespace v8::internal
index 3b3caad..e268920 100644 (file)
@@ -561,6 +561,17 @@ class CallStubCompiler: public StubCompiler {
 };
 
 
+class ConstructStubCompiler: public StubCompiler {
+ public:
+  explicit ConstructStubCompiler() {}
+
+  Object* CompileConstructStub(SharedFunctionInfo* shared);
+
+ private:
+  Object* GetCode();
+};
+
+
 } }  // namespace v8::internal
 
 #endif  // V8_STUB_CACHE_H_
index 43cd5e3..0b941f6 100644 (file)
@@ -141,6 +141,7 @@ namespace internal {
   SC(call_global_inline_miss, V8.CallGlobalInlineMiss)              \
   SC(constructed_objects, V8.ConstructedObjects)                    \
   SC(constructed_objects_runtime, V8.ConstructedObjectsRuntime)     \
+  SC(constructed_objects_stub, V8.ConstructedObjectsStub)           \
   SC(for_in, V8.ForIn)                                              \
   SC(enum_cache_hits, V8.EnumCacheHits)                             \
   SC(enum_cache_misses, V8.EnumCacheMisses)                         \
index 091c826..98975fb 100644 (file)
@@ -1738,6 +1738,18 @@ void StubCompiler::GenerateLoadConstant(JSObject* object,
 }
 
 
+Object* ConstructStubCompiler::CompileConstructStub(
+    SharedFunctionInfo* shared) {
+  // Not implemented yet - just jump to generic stub.
+  Code* code = Builtins::builtin(Builtins::JSConstructStubGeneric);
+  Handle<Code> generic_construct_stub(code);
+  __ Jump(generic_construct_stub, RelocInfo::CODE_TARGET);
+
+  // Return the generated code.
+  return GetCode();
+}
+
+
 #undef __
 
 } }  // namespace v8::internal
index 6dbe5d1..6ee3347 100644 (file)
@@ -59,6 +59,10 @@ function f() {
 break_break_point_hit_count = 0;
 f();
 assertEquals(5, break_break_point_hit_count);
+f();
+assertEquals(10, break_break_point_hit_count);
+f();
+assertEquals(15, break_break_point_hit_count);
 
 // Test step into constructor with builtin constructor.
 function g() {
index b26d651..23df6aa 100755 (executable)
@@ -53,9 +53,11 @@ function f4(x) {
 }
 
 o1_1 = new f1();
+assertEquals(1, o1_1.x, "1");
 o1_2 = new f1();
-assertArrayEquals(["x"], props(o1_1));
-assertArrayEquals(["x"], props(o1_2));
+assertEquals(1, o1_1.x, "2");
+assertArrayEquals(["x"], props(o1_1), "3");
+assertArrayEquals(["x"], props(o1_2), "4");
 
 o2_1 = new f2(0);
 o2_2 = new f2(0);
@@ -76,3 +78,46 @@ o4_1_1 = new f4(1);
 o4_1_2 = new f4(1);
 assertArrayEquals(["x", "y"], props(o4_1_1));
 assertArrayEquals(["x", "y"], props(o4_1_2));
+
+function f5(x, y) {
+  this.x = x;
+  this.y = y;
+}
+
+function f6(x, y) {
+  this.y = y;
+  this.x = x;
+}
+
+function f7(x, y, z) {
+  this.x = x;
+  this.y = y;
+}
+
+function testArgs(fun) {
+  obj = new fun();
+  assertArrayEquals(["x", "y"], props(obj));
+  assertEquals(void 0, obj.x);
+  assertEquals(void 0, obj.y);
+
+  obj = new fun("x");
+  assertArrayEquals(["x", "y"], props(obj));
+  assertEquals("x", obj.x);
+  assertEquals(void 0, obj.y);
+
+  obj = new fun("x", "y");
+  assertArrayEquals(["x", "y"], props(obj));
+  assertEquals("x", obj.x);
+  assertEquals("y", obj.y);
+
+  obj = new fun("x", "y", "z");
+  assertArrayEquals(["x", "y"], props(obj));
+  assertEquals("x", obj.x);
+  assertEquals("y", obj.y);
+}
+
+for (var i = 0; i < 10; i++) {
+  testArgs(f5);
+  testArgs(f6);
+  testArgs(f7);
+}
\ No newline at end of file