Commit 53531ed4 authored by sgjesse@chromium.org's avatar sgjesse@chromium.org

Generate specialized constructor code for constructing simple objects.

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
parent c9827717
...@@ -1342,6 +1342,18 @@ Object* KeyedStoreStubCompiler::CompileStoreField(JSObject* object, ...@@ -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 __ #undef __
} } // namespace v8::internal } } // namespace v8::internal
...@@ -425,6 +425,13 @@ bool Compiler::CompileLazy(Handle<SharedFunctionInfo> shared, ...@@ -425,6 +425,13 @@ bool Compiler::CompileLazy(Handle<SharedFunctionInfo> shared,
// Set the expected number of properties for instances. // Set the expected number of properties for instances.
SetExpectedNofPropertiesFromEstimate(shared, lit->expected_property_count()); 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. // Check the function has compiled code.
ASSERT(shared->is_compiled()); ASSERT(shared->is_compiled());
return true; return true;
......
...@@ -466,9 +466,14 @@ Debug.source = function(f) { ...@@ -466,9 +466,14 @@ Debug.source = function(f) {
return %FunctionGetSourceCode(f); return %FunctionGetSourceCode(f);
}; };
Debug.assembler = function(f) { Debug.disassemble = function(f) {
if (!IS_FUNCTION(f)) throw new Error('Parameters have wrong types.'); 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) { Debug.sourcePosition = function(f) {
......
...@@ -132,7 +132,7 @@ void Builtins::Generate_JSConstructStubGeneric(MacroAssembler* masm) { ...@@ -132,7 +132,7 @@ void Builtins::Generate_JSConstructStubGeneric(MacroAssembler* masm) {
// Make sure that the maximum heap object size will never cause us // Make sure that the maximum heap object size will never cause us
// problem here, because it is always greater than the maximum // problem here, because it is always greater than the maximum
// instance size that can be represented in a byte. // 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 =
ExternalReference::new_space_allocation_top_address(); ExternalReference::new_space_allocation_top_address();
__ mov(ebx, Operand::StaticVariable(new_space_allocation_top)); __ mov(ebx, Operand::StaticVariable(new_space_allocation_top));
......
...@@ -1740,6 +1740,146 @@ Object* KeyedLoadStubCompiler::CompileLoadFunctionPrototype(String* name) { ...@@ -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 __ #undef __
} } // namespace v8::internal } } // namespace v8::internal
...@@ -4800,7 +4800,6 @@ void SharedFunctionInfo::SetThisPropertyAssignmentsInfo( ...@@ -4800,7 +4800,6 @@ void SharedFunctionInfo::SetThisPropertyAssignmentsInfo(
bool only_this_property_assignments, bool only_this_property_assignments,
bool only_simple_this_property_assignments, bool only_simple_this_property_assignments,
FixedArray* assignments) { FixedArray* assignments) {
ASSERT(this_property_assignments()->IsUndefined());
set_compiler_hints(BooleanBit::set(compiler_hints(), set_compiler_hints(BooleanBit::set(compiler_hints(),
kHasOnlyThisPropertyAssignments, kHasOnlyThisPropertyAssignments,
only_this_property_assignments)); only_this_property_assignments));
...@@ -4812,6 +4811,18 @@ void SharedFunctionInfo::SetThisPropertyAssignmentsInfo( ...@@ -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) { String* SharedFunctionInfo::GetThisPropertyAssignmentName(int index) {
Object* obj = this_property_assignments(); Object* obj = this_property_assignments();
ASSERT(obj->IsFixedArray()); ASSERT(obj->IsFixedArray());
...@@ -4822,6 +4833,32 @@ String* SharedFunctionInfo::GetThisPropertyAssignmentName(int index) { ...@@ -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 // Support function for printing the source code to a StringStream
// without any allocation in the heap. // without any allocation in the heap.
void SharedFunctionInfo::SourceCodePrint(StringStream* accumulator, void SharedFunctionInfo::SourceCodePrint(StringStream* accumulator,
......
...@@ -3108,6 +3108,9 @@ class SharedFunctionInfo: public HeapObject { ...@@ -3108,6 +3108,9 @@ class SharedFunctionInfo: public HeapObject {
bool has_only_simple_this_property_assignments, bool has_only_simple_this_property_assignments,
FixedArray* 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 // Indicate that this function only consists of assignments of the form
// this.x = ...;. // this.x = ...;.
inline bool has_only_this_property_assignments(); inline bool has_only_this_property_assignments();
...@@ -3122,6 +3125,9 @@ class SharedFunctionInfo: public HeapObject { ...@@ -3122,6 +3125,9 @@ class SharedFunctionInfo: public HeapObject {
inline int this_property_assignments_count(); inline int this_property_assignments_count();
inline void set_this_property_assignments_count(int value); inline void set_this_property_assignments_count(int value);
String* GetThisPropertyAssignmentName(int index); String* GetThisPropertyAssignmentName(int index);
bool IsThisPropertyAssignmentArgument(int index);
int GetThisPropertyAssignmentArgument(int index);
Object* GetThisPropertyAssignmentConstant(int index);
// [source code]: Source code for the function. // [source code]: Source code for the function.
bool HasSourceCode(); bool HasSourceCode();
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
#include "v8threads.h" #include "v8threads.h"
#include "smart-pointer.h" #include "smart-pointer.h"
#include "parser.h" #include "parser.h"
#include "stub-cache.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
...@@ -1235,6 +1236,9 @@ static Object* Runtime_SetCode(Arguments args) { ...@@ -1235,6 +1236,9 @@ static Object* Runtime_SetCode(Arguments args) {
// Array, and Object, and some web code // Array, and Object, and some web code
// doesn't like seeing source code for constructors. // doesn't like seeing source code for constructors.
target->shared()->set_script(Heap::undefined_value()); 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()); context = Handle<Context>(fun->context());
// Make sure we get a fresh copy of the literal vector to avoid // Make sure we get a fresh copy of the literal vector to avoid
...@@ -4326,11 +4330,21 @@ static Object* Runtime_NewClosure(Arguments args) { ...@@ -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 // TODO(385): Change this to create a construct stub specialized for
// the given map to make allocation of simple objects - and maybe // the given map to make allocation of simple objects - and maybe
// arrays - much faster. // 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) { ...@@ -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(); bool first_allocation = !function->has_initial_map();
Handle<JSObject> result = Factory::NewJSObject(function); Handle<JSObject> result = Factory::NewJSObject(function);
if (first_allocation) { if (first_allocation) {
Handle<Map> map = Handle<Map>(function->initial_map()); 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); function->shared()->set_construct_stub(*stub);
} }
Counters::constructed_objects.Increment(); Counters::constructed_objects.Increment();
Counters::constructed_objects_runtime.Increment(); Counters::constructed_objects_runtime.Increment();
return *result; return *result;
} }
...@@ -7386,7 +7410,7 @@ static Object* Runtime_SystemBreak(Arguments args) { ...@@ -7386,7 +7410,7 @@ static Object* Runtime_SystemBreak(Arguments args) {
} }
static Object* Runtime_FunctionGetAssemblerCode(Arguments args) { static Object* Runtime_DebugDisassembleFunction(Arguments args) {
#ifdef DEBUG #ifdef DEBUG
HandleScope scope; HandleScope scope;
ASSERT(args.length() == 1); ASSERT(args.length() == 1);
...@@ -7401,6 +7425,21 @@ static Object* Runtime_FunctionGetAssemblerCode(Arguments args) { ...@@ -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) { static Object* Runtime_FunctionGetInferredName(Arguments args) {
NoHandleAllocation ha; NoHandleAllocation ha;
ASSERT(args.length() == 1); ASSERT(args.length() == 1);
......
...@@ -303,7 +303,8 @@ namespace internal { ...@@ -303,7 +303,8 @@ namespace internal {
F(DebugConstructedBy, 2) \ F(DebugConstructedBy, 2) \
F(DebugGetPrototype, 1) \ F(DebugGetPrototype, 1) \
F(SystemBreak, 0) \ F(SystemBreak, 0) \
F(FunctionGetAssemblerCode, 1) \ F(DebugDisassembleFunction, 1) \
F(DebugDisassembleConstructor, 1) \
F(FunctionGetInferredName, 1) F(FunctionGetInferredName, 1)
#else #else
#define RUNTIME_FUNCTION_LIST_DEBUGGER_SUPPORT(F) #define RUNTIME_FUNCTION_LIST_DEBUGGER_SUPPORT(F)
......
...@@ -1097,4 +1097,11 @@ Object* CallStubCompiler::GetCode(PropertyType type, String* name) { ...@@ -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 } } // namespace v8::internal
...@@ -561,6 +561,17 @@ class CallStubCompiler: public StubCompiler { ...@@ -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 } } // namespace v8::internal
#endif // V8_STUB_CACHE_H_ #endif // V8_STUB_CACHE_H_
...@@ -141,6 +141,7 @@ namespace internal { ...@@ -141,6 +141,7 @@ namespace internal {
SC(call_global_inline_miss, V8.CallGlobalInlineMiss) \ SC(call_global_inline_miss, V8.CallGlobalInlineMiss) \
SC(constructed_objects, V8.ConstructedObjects) \ SC(constructed_objects, V8.ConstructedObjects) \
SC(constructed_objects_runtime, V8.ConstructedObjectsRuntime) \ SC(constructed_objects_runtime, V8.ConstructedObjectsRuntime) \
SC(constructed_objects_stub, V8.ConstructedObjectsStub) \
SC(for_in, V8.ForIn) \ SC(for_in, V8.ForIn) \
SC(enum_cache_hits, V8.EnumCacheHits) \ SC(enum_cache_hits, V8.EnumCacheHits) \
SC(enum_cache_misses, V8.EnumCacheMisses) \ SC(enum_cache_misses, V8.EnumCacheMisses) \
......
...@@ -1738,6 +1738,18 @@ void StubCompiler::GenerateLoadConstant(JSObject* object, ...@@ -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 __ #undef __
} } // namespace v8::internal } } // namespace v8::internal
...@@ -59,6 +59,10 @@ function f() { ...@@ -59,6 +59,10 @@ function f() {
break_break_point_hit_count = 0; break_break_point_hit_count = 0;
f(); f();
assertEquals(5, break_break_point_hit_count); 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. // Test step into constructor with builtin constructor.
function g() { function g() {
......
...@@ -53,9 +53,11 @@ function f4(x) { ...@@ -53,9 +53,11 @@ function f4(x) {
} }
o1_1 = new f1(); o1_1 = new f1();
assertEquals(1, o1_1.x, "1");
o1_2 = new f1(); o1_2 = new f1();
assertArrayEquals(["x"], props(o1_1)); assertEquals(1, o1_1.x, "2");
assertArrayEquals(["x"], props(o1_2)); assertArrayEquals(["x"], props(o1_1), "3");
assertArrayEquals(["x"], props(o1_2), "4");
o2_1 = new f2(0); o2_1 = new f2(0);
o2_2 = new f2(0); o2_2 = new f2(0);
...@@ -76,3 +78,46 @@ o4_1_1 = new f4(1); ...@@ -76,3 +78,46 @@ o4_1_1 = new f4(1);
o4_1_2 = new f4(1); o4_1_2 = new f4(1);
assertArrayEquals(["x", "y"], props(o4_1_1)); assertArrayEquals(["x", "y"], props(o4_1_1));
assertArrayEquals(["x", "y"], props(o4_1_2)); 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment