Avoid using a register for constant external array indices.

This CL is based on and obsoletes CL 6879037.

TEST=mjsunit/external-array.js

Review URL: http://codereview.chromium.org/6902112

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@7844 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
jkummerow@chromium.org 2011-05-10 15:25:17 +00:00
parent 1eedd8056d
commit 944a388412
12 changed files with 240 additions and 116 deletions

View File

@ -1861,7 +1861,7 @@ LInstruction* LChunkBuilder::DoLoadKeyedSpecializedArrayElement(
array_type == kExternalDoubleArray)));
ASSERT(instr->key()->representation().IsInteger32());
LOperand* external_pointer = UseRegister(instr->external_pointer());
LOperand* key = UseRegister(instr->key());
LOperand* key = UseRegisterOrConstant(instr->key());
LLoadKeyedSpecializedArrayElement* result =
new LLoadKeyedSpecializedArrayElement(external_pointer, key);
LInstruction* load_instr = DefineAsRegister(result);
@ -1919,7 +1919,7 @@ LInstruction* LChunkBuilder::DoStoreKeyedSpecializedArrayElement(
LOperand* val = val_is_temp_register
? UseTempRegister(instr->value())
: UseRegister(instr->value());
LOperand* key = UseRegister(instr->key());
LOperand* key = UseRegisterOrConstant(instr->key());
return new LStoreKeyedSpecializedArrayElement(external_pointer,
key,

View File

@ -2484,40 +2484,56 @@ void LCodeGen::DoLoadKeyedFastElement(LLoadKeyedFastElement* instr) {
void LCodeGen::DoLoadKeyedSpecializedArrayElement(
LLoadKeyedSpecializedArrayElement* instr) {
Register external_pointer = ToRegister(instr->external_pointer());
Register key = ToRegister(instr->key());
Register key = no_reg;
ExternalArrayType array_type = instr->array_type();
if (array_type == kExternalFloatArray) {
bool key_is_constant = instr->key()->IsConstantOperand();
int constant_key = 0;
if (key_is_constant) {
constant_key = ToInteger32(LConstantOperand::cast(instr->key()));
if (constant_key & 0xF0000000) {
Abort("array index constant value too big.");
}
} else {
key = ToRegister(instr->key());
}
int shift_size = ExternalArrayTypeToShiftSize(array_type);
if (array_type == kExternalFloatArray || array_type == kExternalDoubleArray) {
CpuFeatures::Scope scope(VFP3);
DwVfpRegister result(ToDoubleRegister(instr->result()));
__ add(scratch0(), external_pointer, Operand(key, LSL, 2));
__ vldr(result.low(), scratch0(), 0);
__ vcvt_f64_f32(result, result.low());
} else if (array_type == kExternalDoubleArray) {
CpuFeatures::Scope scope(VFP3);
DwVfpRegister result(ToDoubleRegister(instr->result()));
__ add(scratch0(), external_pointer, Operand(key, LSL, 3));
__ vldr(result, scratch0(), 0);
Operand operand(key_is_constant ? Operand(constant_key * (1 << shift_size))
: Operand(key, LSL, shift_size));
__ add(scratch0(), external_pointer, operand);
if (array_type == kExternalFloatArray) {
__ vldr(result.low(), scratch0(), 0);
__ vcvt_f64_f32(result, result.low());
} else { // i.e. array_type == kExternalDoubleArray
__ vldr(result, scratch0(), 0);
}
} else {
Register result(ToRegister(instr->result()));
MemOperand mem_operand(key_is_constant
? MemOperand(external_pointer, constant_key * (1 << shift_size))
: MemOperand(external_pointer, key, LSL, shift_size));
switch (array_type) {
case kExternalByteArray:
__ ldrsb(result, MemOperand(external_pointer, key));
__ ldrsb(result, mem_operand);
break;
case kExternalUnsignedByteArray:
case kExternalPixelArray:
__ ldrb(result, MemOperand(external_pointer, key));
__ ldrb(result, mem_operand);
break;
case kExternalShortArray:
__ ldrsh(result, MemOperand(external_pointer, key, LSL, 1));
__ ldrsh(result, mem_operand);
break;
case kExternalUnsignedShortArray:
__ ldrh(result, MemOperand(external_pointer, key, LSL, 1));
__ ldrh(result, mem_operand);
break;
case kExternalIntArray:
__ ldr(result, MemOperand(external_pointer, key, LSL, 2));
__ ldr(result, mem_operand);
break;
case kExternalUnsignedIntArray:
__ ldr(result, MemOperand(external_pointer, key, LSL, 2));
__ ldr(result, mem_operand);
__ cmp(result, Operand(0x80000000));
// TODO(danno): we could be more clever here, perhaps having a special
// version of the stub that detects if the overflow case actually
@ -3247,39 +3263,53 @@ void LCodeGen::DoStoreKeyedSpecializedArrayElement(
LStoreKeyedSpecializedArrayElement* instr) {
Register external_pointer = ToRegister(instr->external_pointer());
Register key = ToRegister(instr->key());
Register key = no_reg;
ExternalArrayType array_type = instr->array_type();
bool key_is_constant = instr->key()->IsConstantOperand();
int constant_key = 0;
if (key_is_constant) {
constant_key = ToInteger32(LConstantOperand::cast(instr->key()));
if (constant_key & 0xF0000000) {
Abort("array index constant value too big.");
}
} else {
key = ToRegister(instr->key());
}
int shift_size = ExternalArrayTypeToShiftSize(array_type);
if (array_type == kExternalFloatArray) {
if (array_type == kExternalFloatArray || array_type == kExternalDoubleArray) {
CpuFeatures::Scope scope(VFP3);
DwVfpRegister value(ToDoubleRegister(instr->value()));
__ add(scratch0(), external_pointer, Operand(key, LSL, 2));
__ vcvt_f32_f64(double_scratch0().low(), value);
__ vstr(double_scratch0().low(), scratch0(), 0);
} else if (array_type == kExternalDoubleArray) {
CpuFeatures::Scope scope(VFP3);
DwVfpRegister value(ToDoubleRegister(instr->value()));
__ add(scratch0(), external_pointer, Operand(key, LSL, 3));
__ vstr(value, scratch0(), 0);
Operand operand(key_is_constant ? Operand(constant_key * (1 << shift_size))
: Operand(key, LSL, shift_size));
__ add(scratch0(), external_pointer, operand);
if (array_type == kExternalFloatArray) {
__ vcvt_f32_f64(double_scratch0().low(), value);
__ vstr(double_scratch0().low(), scratch0(), 0);
} else { // i.e. array_type == kExternalDoubleArray
__ vstr(value, scratch0(), 0);
}
} else {
Register value(ToRegister(instr->value()));
MemOperand mem_operand(key_is_constant
? MemOperand(external_pointer, constant_key * (1 << shift_size))
: MemOperand(external_pointer, key, LSL, shift_size));
switch (array_type) {
case kExternalPixelArray:
// Clamp the value to [0..255].
__ Usat(value, 8, Operand(value));
__ strb(value, MemOperand(external_pointer, key));
break;
// Fall through to the next case for the store instruction:
case kExternalByteArray:
case kExternalUnsignedByteArray:
__ strb(value, MemOperand(external_pointer, key));
__ strb(value, mem_operand);
break;
case kExternalShortArray:
case kExternalUnsignedShortArray:
__ strh(value, MemOperand(external_pointer, key, LSL, 1));
__ strh(value, mem_operand);
break;
case kExternalIntArray:
case kExternalUnsignedIntArray:
__ str(value, MemOperand(external_pointer, key, LSL, 2));
__ str(value, mem_operand);
break;
case kExternalFloatArray:
case kExternalDoubleArray:

View File

@ -2392,39 +2392,56 @@ void LCodeGen::DoLoadKeyedFastElement(LLoadKeyedFastElement* instr) {
}
Operand LCodeGen::BuildExternalArrayOperand(LOperand* external_pointer,
LOperand* key,
ExternalArrayType array_type) {
Register external_pointer_reg = ToRegister(external_pointer);
int shift_size = ExternalArrayTypeToShiftSize(array_type);
if (key->IsConstantOperand()) {
int constant_value = ToInteger32(LConstantOperand::cast(key));
if (constant_value & 0xF0000000) {
Abort("array index constant value too big");
}
return Operand(external_pointer_reg, constant_value * (1 << shift_size));
} else {
ScaleFactor scale_factor = static_cast<ScaleFactor>(shift_size);
return Operand(external_pointer_reg, ToRegister(key), scale_factor, 0);
}
}
void LCodeGen::DoLoadKeyedSpecializedArrayElement(
LLoadKeyedSpecializedArrayElement* instr) {
Register external_pointer = ToRegister(instr->external_pointer());
Register key = ToRegister(instr->key());
ExternalArrayType array_type = instr->array_type();
Operand operand(BuildExternalArrayOperand(instr->external_pointer(),
instr->key(), array_type));
if (array_type == kExternalFloatArray) {
XMMRegister result(ToDoubleRegister(instr->result()));
__ movss(result, Operand(external_pointer, key, times_4, 0));
__ movss(result, operand);
__ cvtss2sd(result, result);
} else if (array_type == kExternalDoubleArray) {
__ movdbl(ToDoubleRegister(instr->result()),
Operand(external_pointer, key, times_8, 0));
__ movdbl(ToDoubleRegister(instr->result()), operand);
} else {
Register result(ToRegister(instr->result()));
switch (array_type) {
case kExternalByteArray:
__ movsx_b(result, Operand(external_pointer, key, times_1, 0));
__ movsx_b(result, operand);
break;
case kExternalUnsignedByteArray:
case kExternalPixelArray:
__ movzx_b(result, Operand(external_pointer, key, times_1, 0));
__ movzx_b(result, operand);
break;
case kExternalShortArray:
__ movsx_w(result, Operand(external_pointer, key, times_2, 0));
__ movsx_w(result, operand);
break;
case kExternalUnsignedShortArray:
__ movzx_w(result, Operand(external_pointer, key, times_2, 0));
__ movzx_w(result, operand);
break;
case kExternalIntArray:
__ mov(result, Operand(external_pointer, key, times_4, 0));
__ mov(result, operand);
break;
case kExternalUnsignedIntArray:
__ mov(result, Operand(external_pointer, key, times_4, 0));
__ mov(result, operand);
__ test(result, Operand(result));
// TODO(danno): we could be more clever here, perhaps having a special
// version of the stub that detects if the overflow case actually
@ -3108,15 +3125,14 @@ void LCodeGen::DoBoundsCheck(LBoundsCheck* instr) {
void LCodeGen::DoStoreKeyedSpecializedArrayElement(
LStoreKeyedSpecializedArrayElement* instr) {
Register external_pointer = ToRegister(instr->external_pointer());
Register key = ToRegister(instr->key());
ExternalArrayType array_type = instr->array_type();
Operand operand(BuildExternalArrayOperand(instr->external_pointer(),
instr->key(), array_type));
if (array_type == kExternalFloatArray) {
__ cvtsd2ss(xmm0, ToDoubleRegister(instr->value()));
__ movss(Operand(external_pointer, key, times_4, 0), xmm0);
__ movss(operand, xmm0);
} else if (array_type == kExternalDoubleArray) {
__ movdbl(Operand(external_pointer, key, times_8, 0),
ToDoubleRegister(instr->value()));
__ movdbl(operand, ToDoubleRegister(instr->value()));
} else {
Register value = ToRegister(instr->value());
switch (array_type) {
@ -3135,20 +3151,20 @@ void LCodeGen::DoStoreKeyedSpecializedArrayElement(
__ setcc(negative, temp); // 1 if negative, 0 if positive.
__ dec_b(temp); // 0 if negative, 255 if positive.
__ bind(&done);
__ mov_b(Operand(external_pointer, key, times_1, 0), temp);
__ mov_b(operand, temp);
break;
}
case kExternalByteArray:
case kExternalUnsignedByteArray:
__ mov_b(Operand(external_pointer, key, times_1, 0), value);
__ mov_b(operand, value);
break;
case kExternalShortArray:
case kExternalUnsignedShortArray:
__ mov_w(Operand(external_pointer, key, times_2, 0), value);
__ mov_w(operand, value);
break;
case kExternalIntArray:
case kExternalUnsignedIntArray:
__ mov(Operand(external_pointer, key, times_4, 0), value);
__ mov(operand, value);
break;
case kExternalFloatArray:
case kExternalDoubleArray:

View File

@ -229,6 +229,9 @@ class LCodeGen BASE_EMBEDDED {
Register ToRegister(int index) const;
XMMRegister ToDoubleRegister(int index) const;
int ToInteger32(LConstantOperand* op) const;
Operand BuildExternalArrayOperand(LOperand* external_pointer,
LOperand* key,
ExternalArrayType array_type);
// Specific math operations - used from DoUnaryMathOperation.
void EmitIntegerMathAbs(LUnaryMathOperation* instr);

View File

@ -1895,7 +1895,7 @@ LInstruction* LChunkBuilder::DoLoadKeyedSpecializedArrayElement(
array_type == kExternalDoubleArray)));
ASSERT(instr->key()->representation().IsInteger32());
LOperand* external_pointer = UseRegister(instr->external_pointer());
LOperand* key = UseRegister(instr->key());
LOperand* key = UseRegisterOrConstant(instr->key());
LLoadKeyedSpecializedArrayElement* result =
new LLoadKeyedSpecializedArrayElement(external_pointer,
key);
@ -1950,7 +1950,7 @@ LInstruction* LChunkBuilder::DoStoreKeyedSpecializedArrayElement(
ASSERT(instr->key()->representation().IsInteger32());
LOperand* external_pointer = UseRegister(instr->external_pointer());
LOperand* key = UseRegister(instr->key());
LOperand* key = UseRegisterOrConstant(instr->key());
LOperand* temp = NULL;
if (array_type == kExternalPixelArray) {

View File

@ -166,4 +166,25 @@ void LPointerMap::PrintTo(StringStream* stream) {
}
int ExternalArrayTypeToShiftSize(ExternalArrayType type) {
switch (type) {
case kExternalByteArray:
case kExternalUnsignedByteArray:
case kExternalPixelArray:
return 0;
case kExternalShortArray:
case kExternalUnsignedShortArray:
return 1;
case kExternalIntArray:
case kExternalUnsignedIntArray:
case kExternalFloatArray:
return 2;
case kExternalDoubleArray:
return 3;
}
UNREACHABLE();
return 0;
}
} } // namespace v8::internal

View File

@ -588,6 +588,10 @@ class DeepIterator BASE_EMBEDDED {
ShallowIterator current_iterator_;
};
int ExternalArrayTypeToShiftSize(ExternalArrayType type);
} } // namespace v8::internal
#endif // V8_LITHIUM_H_

View File

@ -2403,39 +2403,56 @@ void LCodeGen::DoLoadKeyedFastElement(LLoadKeyedFastElement* instr) {
}
Operand LCodeGen::BuildExternalArrayOperand(LOperand* external_pointer,
LOperand* key,
ExternalArrayType array_type) {
Register external_pointer_reg = ToRegister(external_pointer);
int shift_size = ExternalArrayTypeToShiftSize(array_type);
if (key->IsConstantOperand()) {
int constant_value = ToInteger32(LConstantOperand::cast(key));
if (constant_value & 0xF0000000) {
Abort("array index constant value too big");
}
return Operand(external_pointer_reg, constant_value * (1 << shift_size));
} else {
ScaleFactor scale_factor = static_cast<ScaleFactor>(shift_size);
return Operand(external_pointer_reg, ToRegister(key), scale_factor, 0);
}
}
void LCodeGen::DoLoadKeyedSpecializedArrayElement(
LLoadKeyedSpecializedArrayElement* instr) {
Register external_pointer = ToRegister(instr->external_pointer());
Register key = ToRegister(instr->key());
ExternalArrayType array_type = instr->array_type();
Operand operand(BuildExternalArrayOperand(instr->external_pointer(),
instr->key(), array_type));
if (array_type == kExternalFloatArray) {
XMMRegister result(ToDoubleRegister(instr->result()));
__ movss(result, Operand(external_pointer, key, times_4, 0));
__ movss(result, operand);
__ cvtss2sd(result, result);
} else if (array_type == kExternalDoubleArray) {
__ movsd(ToDoubleRegister(instr->result()),
Operand(external_pointer, key, times_8, 0));
__ movsd(ToDoubleRegister(instr->result()), operand);
} else {
Register result(ToRegister(instr->result()));
switch (array_type) {
case kExternalByteArray:
__ movsxbq(result, Operand(external_pointer, key, times_1, 0));
__ movsxbq(result, operand);
break;
case kExternalUnsignedByteArray:
case kExternalPixelArray:
__ movzxbq(result, Operand(external_pointer, key, times_1, 0));
__ movzxbq(result, operand);
break;
case kExternalShortArray:
__ movsxwq(result, Operand(external_pointer, key, times_2, 0));
__ movsxwq(result, operand);
break;
case kExternalUnsignedShortArray:
__ movzxwq(result, Operand(external_pointer, key, times_2, 0));
__ movzxwq(result, operand);
break;
case kExternalIntArray:
__ movsxlq(result, Operand(external_pointer, key, times_4, 0));
__ movsxlq(result, operand);
break;
case kExternalUnsignedIntArray:
__ movl(result, Operand(external_pointer, key, times_4, 0));
__ movl(result, operand);
__ testl(result, result);
// TODO(danno): we could be more clever here, perhaps having a special
// version of the stub that detects if the overflow case actually
@ -3097,16 +3114,15 @@ void LCodeGen::DoStoreNamedGeneric(LStoreNamedGeneric* instr) {
void LCodeGen::DoStoreKeyedSpecializedArrayElement(
LStoreKeyedSpecializedArrayElement* instr) {
Register external_pointer = ToRegister(instr->external_pointer());
Register key = ToRegister(instr->key());
ExternalArrayType array_type = instr->array_type();
Operand operand(BuildExternalArrayOperand(instr->external_pointer(),
instr->key(), array_type));
if (array_type == kExternalFloatArray) {
XMMRegister value(ToDoubleRegister(instr->value()));
__ cvtsd2ss(value, value);
__ movss(Operand(external_pointer, key, times_4, 0), value);
__ movss(operand, value);
} else if (array_type == kExternalDoubleArray) {
__ movsd(Operand(external_pointer, key, times_8, 0),
ToDoubleRegister(instr->value()));
__ movsd(operand, ToDoubleRegister(instr->value()));
} else {
Register value(ToRegister(instr->value()));
switch (array_type) {
@ -3118,20 +3134,20 @@ void LCodeGen::DoStoreKeyedSpecializedArrayElement(
__ setcc(negative, value); // 1 if negative, 0 if positive.
__ decb(value); // 0 if negative, 255 if positive.
__ bind(&done);
__ movb(Operand(external_pointer, key, times_1, 0), value);
__ movb(operand, value);
}
break;
case kExternalByteArray:
case kExternalUnsignedByteArray:
__ movb(Operand(external_pointer, key, times_1, 0), value);
__ movb(operand, value);
break;
case kExternalShortArray:
case kExternalUnsignedShortArray:
__ movw(Operand(external_pointer, key, times_2, 0), value);
__ movw(operand, value);
break;
case kExternalIntArray:
case kExternalUnsignedIntArray:
__ movl(Operand(external_pointer, key, times_4, 0), value);
__ movl(operand, value);
break;
case kExternalFloatArray:
case kExternalDoubleArray:

View File

@ -214,6 +214,9 @@ class LCodeGen BASE_EMBEDDED {
Register ToRegister(int index) const;
XMMRegister ToDoubleRegister(int index) const;
Operand BuildExternalArrayOperand(LOperand* external_pointer,
LOperand* key,
ExternalArrayType array_type);
// Specific math operations - used from DoUnaryMathOperation.
void EmitIntegerMathAbs(LUnaryMathOperation* instr);

View File

@ -1851,7 +1851,7 @@ LInstruction* LChunkBuilder::DoLoadKeyedSpecializedArrayElement(
array_type == kExternalDoubleArray)));
ASSERT(instr->key()->representation().IsInteger32());
LOperand* external_pointer = UseRegister(instr->external_pointer());
LOperand* key = UseRegister(instr->key());
LOperand* key = UseRegisterOrConstant(instr->key());
LLoadKeyedSpecializedArrayElement* result =
new LLoadKeyedSpecializedArrayElement(external_pointer, key);
LInstruction* load_instr = DefineAsRegister(result);
@ -1908,7 +1908,7 @@ LInstruction* LChunkBuilder::DoStoreKeyedSpecializedArrayElement(
LOperand* val = val_is_temp_register
? UseTempRegister(instr->value())
: UseRegister(instr->value());
LOperand* key = UseRegister(instr->key());
LOperand* key = UseRegisterOrConstant(instr->key());
return new LStoreKeyedSpecializedArrayElement(external_pointer,
key,

View File

@ -11953,35 +11953,6 @@ static void ExternalArrayTestHelper(v8::ExternalArrayType array_type,
CHECK_EQ(true, result->BooleanValue());
}
// Test crankshaft external array loads
for (int i = 0; i < kElementCount; i++) {
array->set(i, static_cast<ElementType>(i));
}
result = CompileRun("function ee_load_test_func(sum) {"
" for (var i = 0; i < 40; ++i)"
" sum += ext_array[i];"
" return sum;"
"}"
"sum=0;"
"for (var i=0;i<10000;++i) {"
" sum=ee_load_test_func(sum);"
"}"
"sum;");
CHECK_EQ(7800000, result->Int32Value());
// Test crankshaft external array stores
result = CompileRun("function ee_store_test_func(sum) {"
" for (var i = 0; i < 40; ++i)"
" sum += ext_array[i] = i;"
" return sum;"
"}"
"sum=0;"
"for (var i=0;i<10000;++i) {"
" sum=ee_store_test_func(sum);"
"}"
"sum;");
CHECK_EQ(7800000, result->Int32Value());
for (int i = 0; i < kElementCount; i++) {
array->set(i, static_cast<ElementType>(i));
}

View File

@ -25,7 +25,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax
// Flags: --allow-natives-syntax --expose-gc
// This is a regression test for overlapping key and value registers.
function f(a) {
@ -43,15 +43,6 @@ f(a);
assertEquals(0, a[0]);
assertEquals(0, a[1]);
// Test the correct behavior of the |length| property (which is read-only).
a = new Int32Array(42);
assertEquals(42, a.length);
a.length = 2;
assertEquals(42, a.length);
assertTrue(delete a.length);
a.length = 2
assertEquals(2, a.length);
// Test the correct behavior of the |BYTES_PER_ELEMENT| property (which is
// "constant", but not read-only).
a = new Int32Array(2);
@ -88,3 +79,72 @@ for (var i = 0; i < 5; i++) {
%OptimizeFunctionOnNextCall(get);
assertEquals(2.5, get(array, 0));
assertEquals(3.5, get(array, 1));
// Test loads and stores.
types = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array,
Uint32Array, PixelArray, Float32Array, Float64Array];
const kElementCount = 40;
function test_load(array, sum) {
for (var i = 0; i < kElementCount; i++) {
sum += array[i];
}
return sum;
}
function test_load_const_key(array, sum) {
sum += array[0];
sum += array[1];
sum += array[2];
return sum;
}
function test_store(array, sum) {
for (var i = 0; i < kElementCount; i++) {
sum += array[i] = i+1;
}
return sum;
}
function test_store_const_key(array, sum) {
sum += array[0] = 1;
sum += array[1] = 2;
sum += array[2] = 3;
return sum;
}
function run_test(test_func, array, expected_sum_per_run) {
for (var i = 0; i < 5; i++) test_func(array, 0);
%OptimizeFunctionOnNextCall(test_func);
const kRuns = 10;
var sum = 0;
for (var i = 0; i < kRuns; i++) {
sum = test_func(array, sum);
}
assertEquals(sum, expected_sum_per_run * kRuns);
%DeoptimizeFunction(test_func);
gc(); // Makes V8 forget about type information for test_func.
}
for (var t = 0; t < types.length; t++) {
var type = types[t];
var a = new type(kElementCount);
for (var i = 0; i < kElementCount; i++) {
a[i] = i;
}
// Run test functions defined above.
run_test(test_load, a, 780);
run_test(test_load_const_key, a, 3);
run_test(test_store, a, 820);
run_test(test_store_const_key, a, 6);
// Test the correct behavior of the |length| property (which is read-only).
assertEquals(kElementCount, a.length);
a.length = 2;
assertEquals(kElementCount, a.length);
assertTrue(delete a.length);
a.length = 2
assertEquals(2, a.length);
}