Commit 4e03645d authored by sgjesse@chromium.org's avatar sgjesse@chromium.org

Analyze functions for assignment to this properties.

During parsing functions are analyzed for statements of the form this.x = ...;. These assignments are categorized in two types: simple and non simple. The simple ones are where the right hand side is known to be either a constant or an argument to the function. If a function only contains statements of this type the property names are collected and for the simple assignments the index of the argument or the constant value assigned are stored as well.

When the initial map for a function is created and the function consists of only this type of assignemnts the initial map is created with a descriptor array describing these properties which will be known to always exist in an object created from the function.

The information on this property assignments is not collected during pre-parsing so if compiling using pre-parse data these optimization hints are not available.

Next step will be to use the information collected for the simple assignments to generate constructor code which will create and initialize the object from this information without calling the code for the function.
Review URL: http://codereview.chromium.org/172088

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@2710 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 03a39152
......@@ -111,6 +111,7 @@ AST_NODE_LIST(DEF_FORWARD_DECLARATION)
// Typedef only introduced to avoid unreadable code.
// Please do appreciate the required space in "> >".
typedef ZoneList<Handle<String> > ZoneStringList;
typedef ZoneList<Handle<Object> > ZoneObjectList;
class AstNode: public ZoneObject {
......@@ -1226,6 +1227,9 @@ class FunctionLiteral: public Expression {
int materialized_literal_count,
bool contains_array_literal,
int expected_property_count,
bool has_only_this_property_assignments,
bool has_only_simple_this_property_assignments,
Handle<FixedArray> this_property_assignments,
int num_parameters,
int start_position,
int end_position,
......@@ -1236,6 +1240,10 @@ class FunctionLiteral: public Expression {
materialized_literal_count_(materialized_literal_count),
contains_array_literal_(contains_array_literal),
expected_property_count_(expected_property_count),
has_only_this_property_assignments_(has_only_this_property_assignments),
has_only_simple_this_property_assignments_(
has_only_simple_this_property_assignments),
this_property_assignments_(this_property_assignments),
num_parameters_(num_parameters),
start_position_(start_position),
end_position_(end_position),
......@@ -1265,6 +1273,15 @@ class FunctionLiteral: public Expression {
int materialized_literal_count() { return materialized_literal_count_; }
bool contains_array_literal() { return contains_array_literal_; }
int expected_property_count() { return expected_property_count_; }
bool has_only_this_property_assignments() {
return has_only_this_property_assignments_;
}
bool has_only_simple_this_property_assignments() {
return has_only_simple_this_property_assignments_;
}
Handle<FixedArray> this_property_assignments() {
return this_property_assignments_;
}
int num_parameters() { return num_parameters_; }
bool AllowsLazyCompilation();
......@@ -1291,6 +1308,9 @@ class FunctionLiteral: public Expression {
int materialized_literal_count_;
bool contains_array_literal_;
int expected_property_count_;
bool has_only_this_property_assignments_;
bool has_only_simple_this_property_assignments_;
Handle<FixedArray> this_property_assignments_;
int num_parameters_;
int start_position_;
int end_position_;
......
......@@ -255,6 +255,10 @@ void CodeGenerator::SetFunctionInfo(Handle<JSFunction> fun,
fun->shared()->set_is_expression(lit->is_expression());
fun->shared()->set_is_toplevel(is_toplevel);
fun->shared()->set_inferred_name(*lit->inferred_name());
fun->shared()->SetThisPropertyAssignmentsInfo(
lit->has_only_this_property_assignments(),
lit->has_only_simple_this_property_assignments(),
*lit->this_property_assignments());
}
......
......@@ -1054,6 +1054,7 @@ Object* Heap::AllocateMap(InstanceType instance_type, int instance_size) {
map->set_constructor(null_value());
map->set_instance_size(instance_size);
map->set_inobject_properties(0);
map->set_pre_allocated_property_fields(0);
map->set_instance_descriptors(empty_descriptor_array());
map->set_code_cache(empty_fixed_array());
map->set_unused_property_fields(0);
......@@ -1611,6 +1612,9 @@ Object* Heap::AllocateSharedFunctionInfo(Object* name) {
share->set_start_position_and_type(0);
share->set_debug_info(undefined_value());
share->set_inferred_name(empty_string());
share->set_compiler_hints(0);
share->set_this_property_assignments_count(0);
share->set_this_property_assignments(undefined_value());
return result;
}
......@@ -2050,16 +2054,10 @@ Object* Heap::AllocateArgumentsObject(Object* callee, int length) {
Object* Heap::AllocateInitialMap(JSFunction* fun) {
ASSERT(!fun->has_initial_map());
// First create a new map with the expected number of properties being
// allocated in-object.
int expected_nof_properties = fun->shared()->expected_nof_properties();
int instance_size = JSObject::kHeaderSize +
expected_nof_properties * kPointerSize;
if (instance_size > JSObject::kMaxInstanceSize) {
instance_size = JSObject::kMaxInstanceSize;
expected_nof_properties = (instance_size - JSObject::kHeaderSize) /
kPointerSize;
}
// First create a new map with the size and number of in-object properties
// suggested by the function.
int instance_size = fun->shared()->CalculateInstanceSize();
int in_object_properties = fun->shared()->CalculateInObjectProperties();
Object* map_obj = Heap::AllocateMap(JS_OBJECT_TYPE, instance_size);
if (map_obj->IsFailure()) return map_obj;
......@@ -2072,9 +2070,33 @@ Object* Heap::AllocateInitialMap(JSFunction* fun) {
if (prototype->IsFailure()) return prototype;
}
Map* map = Map::cast(map_obj);
map->set_inobject_properties(expected_nof_properties);
map->set_unused_property_fields(expected_nof_properties);
map->set_inobject_properties(in_object_properties);
map->set_unused_property_fields(in_object_properties);
map->set_prototype(prototype);
// If the function has only simple this property assignments add field
// descriptors for these to the initial map as the object cannot be
// constructed without having these properties.
ASSERT(in_object_properties <= Map::kMaxPreAllocatedPropertyFields);
if (fun->shared()->has_only_this_property_assignments() &&
fun->shared()->this_property_assignments_count() > 0) {
int count = fun->shared()->this_property_assignments_count();
if (count > in_object_properties) {
count = in_object_properties;
}
DescriptorArray* descriptors = *Factory::NewDescriptorArray(count);
if (descriptors->IsFailure()) return descriptors;
for (int i = 0; i < count; i++) {
String* name = fun->shared()->GetThisPropertyAssignmentName(i);
ASSERT(name->IsSymbol());
FieldDescriptor field(name, i, NONE);
descriptors->Set(i, &field);
}
descriptors->Sort();
map->set_instance_descriptors(descriptors);
map->set_pre_allocated_property_fields(count);
map->set_unused_property_fields(in_object_properties - count);
}
return map;
}
......@@ -2106,7 +2128,11 @@ Object* Heap::AllocateJSObjectFromMap(Map* map, PretenureFlag pretenure) {
ASSERT(map->instance_type() != JS_BUILTINS_OBJECT_TYPE);
// Allocate the backing storage for the properties.
int prop_size = map->unused_property_fields() - map->inobject_properties();
int prop_size =
map->pre_allocated_property_fields() +
map->unused_property_fields() -
map->inobject_properties();
ASSERT(prop_size >= 0);
Object* properties = AllocateFixedArray(prop_size, pretenure);
if (properties->IsFailure()) return properties;
......@@ -2599,6 +2625,7 @@ Object* Heap::CopyFixedArray(FixedArray* src) {
Object* Heap::AllocateFixedArray(int length) {
ASSERT(length >= 0);
if (length == 0) return empty_fixed_array();
Object* result = AllocateRawFixedArray(length);
if (!result->IsFailure()) {
......
......@@ -180,12 +180,16 @@ void Builtins::Generate_JSConstructStubGeneric(MacroAssembler* masm) {
// eax: initial map
// ebx: JSObject
// edi: start of next object
// Calculate the total number of properties described by the map.
__ movzx_b(edx, FieldOperand(eax, Map::kUnusedPropertyFieldsOffset));
__ movzx_b(ecx, FieldOperand(eax, Map::kInObjectPropertiesOffset));
__ movzx_b(ecx, FieldOperand(eax, Map::kPreAllocatedPropertyFieldsOffset));
__ add(edx, Operand(ecx));
// Calculate unused properties past the end of the in-object properties.
__ movzx_b(ecx, FieldOperand(eax, Map::kInObjectPropertiesOffset));
__ sub(edx, Operand(ecx));
// Done if no extra properties are to be allocated.
__ j(zero, &allocated);
__ Assert(positive, "Property allocation count failed.");
// Scale the number of elements by pointer size and add the header for
// FixedArrays to the start of the next object calculation from above.
......@@ -321,6 +325,7 @@ void Builtins::Generate_JSConstructStubGeneric(MacroAssembler* masm) {
__ pop(ecx);
__ lea(esp, Operand(esp, ebx, times_2, 1 * kPointerSize)); // 1 ~ receiver
__ push(ecx);
__ IncrementCounter(&Counters::constructed_objects, 1);
__ ret(0);
}
......
......@@ -371,9 +371,9 @@ void JSObject::JSObjectVerify() {
VerifyHeapPointer(properties());
VerifyHeapPointer(elements());
if (HasFastProperties()) {
CHECK(map()->unused_property_fields() ==
(map()->inobject_properties() + properties()->length() -
map()->NextFreePropertyIndex()));
CHECK_EQ(map()->unused_property_fields(),
(map()->inobject_properties() + properties()->length() -
map()->NextFreePropertyIndex()));
}
}
......@@ -462,6 +462,7 @@ void Map::MapPrint() {
HeapObject::PrintHeader("Map");
PrintF(" - type: %s\n", TypeToString(instance_type()));
PrintF(" - instance size: %d\n", instance_size());
PrintF(" - inobject properties: %d\n", inobject_properties());
PrintF(" - unused property fields: %d\n", unused_property_fields());
if (is_hidden_prototype()) {
PrintF(" - hidden_prototype\n");
......@@ -619,6 +620,12 @@ void SharedFunctionInfo::SharedFunctionInfoPrint() {
PrintF("\n - debug info = ");
debug_info()->ShortPrint();
PrintF("\n - length = %d", length());
PrintF("\n - has_only_this_property_assignments = %d",
has_only_this_property_assignments());
PrintF("\n - has_only_simple_this_property_assignments = %d",
has_only_simple_this_property_assignments());
PrintF("\n - this_property_assignments = ");
this_property_assignments()->ShortPrint();
PrintF("\n");
}
......
......@@ -91,7 +91,13 @@ PropertyDetails PropertyDetails::AsDeleted() {
}
#define BOOL_ACCESSORS(holder, field, name, offset) \
#define BOOL_GETTER(holder, field, name, offset) \
bool holder::name() { \
return BooleanBit::get(field(), offset); \
} \
#define BOOL_ACCESSORS(holder, field, name, offset) \
bool holder::name() { \
return BooleanBit::get(field(), offset); \
} \
......@@ -1937,6 +1943,11 @@ int Map::inobject_properties() {
}
int Map::pre_allocated_property_fields() {
return READ_BYTE_FIELD(this, kPreAllocatedPropertyFieldsOffset);
}
int HeapObject::SizeFromMap(Map* map) {
InstanceType instance_type = map->instance_type();
// Only inline the most frequent cases.
......@@ -1969,6 +1980,14 @@ void Map::set_inobject_properties(int value) {
}
void Map::set_pre_allocated_property_fields(int value) {
ASSERT(0 <= value && value < 256);
WRITE_BYTE_FIELD(this,
kPreAllocatedPropertyFieldsOffset,
static_cast<byte>(value));
}
InstanceType Map::instance_type() {
return static_cast<InstanceType>(READ_BYTE_FIELD(this, kInstanceTypeOffset));
}
......@@ -2298,6 +2317,8 @@ ACCESSORS(SharedFunctionInfo, function_data, Object,
ACCESSORS(SharedFunctionInfo, script, Object, kScriptOffset)
ACCESSORS(SharedFunctionInfo, debug_info, Object, kDebugInfoOffset)
ACCESSORS(SharedFunctionInfo, inferred_name, String, kInferredNameOffset)
ACCESSORS(SharedFunctionInfo, this_property_assignments, Object,
kThisPropertyAssignmentsOffset)
BOOL_ACCESSORS(FunctionTemplateInfo, flag, hidden_prototype,
kHiddenPrototypeBit)
......@@ -2308,6 +2329,13 @@ BOOL_ACCESSORS(SharedFunctionInfo, start_position_and_type, is_expression,
kIsExpressionBit)
BOOL_ACCESSORS(SharedFunctionInfo, start_position_and_type, is_toplevel,
kIsTopLevelBit)
BOOL_GETTER(SharedFunctionInfo, compiler_hints,
has_only_this_property_assignments,
kHasOnlyThisPropertyAssignments)
BOOL_GETTER(SharedFunctionInfo, compiler_hints,
has_only_simple_this_property_assignments,
kHasOnlySimpleThisPropertyAssignments)
INT_ACCESSORS(SharedFunctionInfo, length, kLengthOffset)
INT_ACCESSORS(SharedFunctionInfo, formal_parameter_count,
......@@ -2319,6 +2347,10 @@ INT_ACCESSORS(SharedFunctionInfo, start_position_and_type,
INT_ACCESSORS(SharedFunctionInfo, end_position, kEndPositionOffset)
INT_ACCESSORS(SharedFunctionInfo, function_token_position,
kFunctionTokenPositionOffset)
INT_ACCESSORS(SharedFunctionInfo, compiler_hints,
kCompilerHintsOffset)
INT_ACCESSORS(SharedFunctionInfo, this_property_assignments_count,
kThisPropertyAssignmentsCountOffset)
void SharedFunctionInfo::DontAdaptArguments() {
......
......@@ -4780,6 +4780,48 @@ Object* SharedFunctionInfo::GetSourceCode() {
}
int SharedFunctionInfo::CalculateInstanceSize() {
int instance_size =
JSObject::kHeaderSize +
expected_nof_properties() * kPointerSize;
if (instance_size > JSObject::kMaxInstanceSize) {
instance_size = JSObject::kMaxInstanceSize;
}
return instance_size;
}
int SharedFunctionInfo::CalculateInObjectProperties() {
return (CalculateInstanceSize() - JSObject::kHeaderSize) / kPointerSize;
}
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));
set_compiler_hints(BooleanBit::set(compiler_hints(),
kHasOnlySimpleThisPropertyAssignments,
only_simple_this_property_assignments));
set_this_property_assignments(assignments);
set_this_property_assignments_count(assignments->length() / 3);
}
String* SharedFunctionInfo::GetThisPropertyAssignmentName(int index) {
Object* obj = this_property_assignments();
ASSERT(obj->IsFixedArray());
ASSERT(index < this_property_assignments_count());
obj = FixedArray::cast(obj)->get(index * 3);
ASSERT(obj->IsString());
return String::cast(obj);
}
// Support function for printing the source code to a StringStream
// without any allocation in the heap.
void SharedFunctionInfo::SourceCodePrint(StringStream* accumulator,
......@@ -4826,6 +4868,8 @@ void SharedFunctionInfo::SharedFunctionInfoIterateBody(ObjectVisitor* v) {
IteratePointers(v, kNameOffset, kConstructStubOffset + kPointerSize);
IteratePointers(v, kInstanceClassNameOffset, kScriptOffset + kPointerSize);
IteratePointers(v, kDebugInfoOffset, kInferredNameOffset + kPointerSize);
IteratePointers(v, kThisPropertyAssignmentsOffset,
kThisPropertyAssignmentsOffset + kPointerSize);
}
......
......@@ -2704,6 +2704,10 @@ class Map: public HeapObject {
inline int inobject_properties();
inline void set_inobject_properties(int value);
// Count of property fields pre-allocated in the object when first allocated.
inline int pre_allocated_property_fields();
inline void set_pre_allocated_property_fields(int value);
// Instance type.
inline InstanceType instance_type();
inline void set_instance_type(InstanceType value);
......@@ -2869,6 +2873,8 @@ class Map: public HeapObject {
void MapVerify();
#endif
static const int kMaxPreAllocatedPropertyFields = 255;
// Layout description.
static const int kInstanceSizesOffset = HeapObject::kHeaderSize;
static const int kInstanceAttributesOffset = kInstanceSizesOffset + kIntSize;
......@@ -2882,7 +2888,8 @@ class Map: public HeapObject {
// Byte offsets within kInstanceSizesOffset.
static const int kInstanceSizeOffset = kInstanceSizesOffset + 0;
static const int kInObjectPropertiesOffset = kInstanceSizesOffset + 1;
// The bytes at positions 2 and 3 are not in use at the moment.
static const int kPreAllocatedPropertyFieldsOffset = kInstanceSizesOffset + 2;
// The byte at position 3 is not in use at the moment.
// Byte offsets within kInstanceAttributesOffset attributes.
static const int kInstanceTypeOffset = kInstanceAttributesOffset + 0;
......@@ -3090,10 +3097,42 @@ class SharedFunctionInfo: public HeapObject {
inline bool is_toplevel();
inline void set_is_toplevel(bool value);
// Bit field containing various information collected by the compiler to
// drive optimization.
inline int compiler_hints();
inline void set_compiler_hints(int value);
// Add information on assignments of the form this.x = ...;
void SetThisPropertyAssignmentsInfo(
bool has_only_this_property_assignments,
bool has_only_simple_this_property_assignments,
FixedArray* this_property_assignments);
// Indicate that this function only consists of assignments of the form
// this.x = ...;.
inline bool has_only_this_property_assignments();
// Indicate that this function only consists of assignments of the form
// this.x = y; where y is either a constant or refers to an argument.
inline bool has_only_simple_this_property_assignments();
// For functions which only contains this property assignments this provides
// access to the names for the properties assigned.
DECL_ACCESSORS(this_property_assignments, Object)
inline int this_property_assignments_count();
inline void set_this_property_assignments_count(int value);
String* GetThisPropertyAssignmentName(int index);
// [source code]: Source code for the function.
bool HasSourceCode();
Object* GetSourceCode();
// Calculate the instance size.
int CalculateInstanceSize();
// Calculate the number of in-object properties.
int CalculateInObjectProperties();
// Dispatched behavior.
void SharedFunctionInfoIterateBody(ObjectVisitor* v);
// Set max_length to -1 for unlimited length.
......@@ -3129,7 +3168,12 @@ class SharedFunctionInfo: public HeapObject {
static const int kScriptOffset = kExternalReferenceDataOffset + kPointerSize;
static const int kDebugInfoOffset = kScriptOffset + kPointerSize;
static const int kInferredNameOffset = kDebugInfoOffset + kPointerSize;
static const int kSize = kInferredNameOffset + kPointerSize;
static const int kCompilerHintsOffset = kInferredNameOffset + kPointerSize;
static const int kThisPropertyAssignmentsOffset =
kCompilerHintsOffset + kPointerSize;
static const int kThisPropertyAssignmentsCountOffset =
kThisPropertyAssignmentsOffset + kPointerSize;
static const int kSize = kThisPropertyAssignmentsCountOffset + kPointerSize;
private:
// Bit positions in length_and_flg.
......@@ -3146,6 +3190,10 @@ class SharedFunctionInfo: public HeapObject {
static const int kStartPositionShift = 2;
static const int kStartPositionMask = ~((1 << kStartPositionShift) - 1);
// Bit positions in compiler_hints.
static const int kHasOnlyThisPropertyAssignments = 0;
static const int kHasOnlySimpleThisPropertyAssignments = 1;
DISALLOW_IMPLICIT_CONSTRUCTORS(SharedFunctionInfo);
};
......
This diff is collapsed.
......@@ -4380,6 +4380,8 @@ static Object* Runtime_NewObject(Arguments args) {
Handle<Code> stub = ComputeConstructStub(map);
function->shared()->set_construct_stub(*stub);
}
Counters::constructed_objects.Increment();
Counters::constructed_objects_runtime.Increment();
return *result;
}
......
......@@ -139,6 +139,8 @@ namespace internal {
SC(named_store_global_inline_miss, V8.NamedStoreGlobalInlineMiss) \
SC(call_global_inline, V8.CallGlobalInline) \
SC(call_global_inline_miss, V8.CallGlobalInlineMiss) \
SC(constructed_objects, V8.ConstructedObjects) \
SC(constructed_objects_runtime, V8.ConstructedObjectsRuntime) \
SC(for_in, V8.ForIn) \
SC(enum_cache_hits, V8.EnumCacheHits) \
SC(enum_cache_misses, V8.EnumCacheMisses) \
......
......@@ -585,12 +585,16 @@ void Builtins::Generate_JSConstructStubGeneric(MacroAssembler* masm) {
// rax: initial map
// rbx: JSObject
// rdi: start of next object
// Calculate total properties described map.
__ movzxbq(rdx, FieldOperand(rax, Map::kUnusedPropertyFieldsOffset));
__ movzxbq(rcx, FieldOperand(rax, Map::kInObjectPropertiesOffset));
__ movzxbq(rcx, FieldOperand(rax, Map::kPreAllocatedPropertyFieldsOffset));
__ addq(rdx, rcx);
// Calculate unused properties past the end of the in-object properties.
__ movzxbq(rcx, FieldOperand(rax, Map::kInObjectPropertiesOffset));
__ subq(rdx, rcx);
// Done if no extra properties are to be allocated.
__ j(zero, &allocated);
__ Assert(positive, "Property allocation count failed.");
// Scale the number of elements by pointer size and add the header for
// FixedArrays to the start of the next object calculation from above.
......@@ -726,6 +730,7 @@ void Builtins::Generate_JSConstructStubGeneric(MacroAssembler* masm) {
__ pop(rcx);
__ lea(rsp, Operand(rsp, rbx, times_4, 1 * kPointerSize)); // 1 ~ receiver
__ push(rcx);
__ IncrementCounter(&Counters::constructed_objects, 1);
__ ret(0);
}
......
// Copyright 2008 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
function props(x) {
var array = [];
for (var p in x) array.push(p);
return array.sort();
}
function f1() {
this.x = 1;
}
function f2(x) {
this.x = x;
}
function f3(x) {
this.x = x;
this.y = 1;
this.z = f1;
}
function f4(x) {
this.x = x;
this.y = 1;
if (x == 1) return;
this.z = f1;
}
o1_1 = new f1();
o1_2 = new f1();
assertArrayEquals(["x"], props(o1_1));
assertArrayEquals(["x"], props(o1_2));
o2_1 = new f2(0);
o2_2 = new f2(0);
assertArrayEquals(["x"], props(o2_1));
assertArrayEquals(["x"], props(o2_2));
o3_1 = new f3(0);
o3_2 = new f3(0);
assertArrayEquals(["x", "y", "z"], props(o3_1));
assertArrayEquals(["x", "y", "z"], props(o3_2));
o4_0_1 = new f4(0);
o4_0_2 = new f4(0);
assertArrayEquals(["x", "y", "z"], props(o4_0_1));
assertArrayEquals(["x", "y", "z"], props(o4_0_2));
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));
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