DISALLOW_COPY_AND_ASSIGN(ElementsTransitionGenerator);
};
+
+class StringCharLoadGenerator : public AllStatic {
+ public:
+ // Generates the code for handling different string types and loading the
+ // indexed character into |result|. We expect |index| as untagged input and
+ // |result| as untagged output.
+ static void Generate(MacroAssembler* masm,
+ Factory* factory,
+ Register string,
+ Register index,
+ Register result,
+ Label* call_runtime);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StringCharLoadGenerator);
+};
+
} } // namespace v8::internal
#endif // V8_CODEGEN_H_
ExternalString::kResourceOffset -
kHeapObjectTag);
+ // Clear pointer cache.
+ ExternalString::cast(string)->clear_data_cache();
+
// Dispose of the C++ object if it has not already been disposed.
if (*resource_addr != NULL) {
(*resource_addr)->Dispose();
// Copy first part.
const char* src;
if (first->IsExternalString()) {
- src = ExternalAsciiString::cast(first)->resource()->data();
+ src = ExternalAsciiString::cast(first)->GetChars();
} else {
src = SeqAsciiString::cast(first)->GetChars();
}
for (int i = 0; i < first_length; i++) *dest++ = src[i];
// Copy second part.
if (second->IsExternalString()) {
- src = ExternalAsciiString::cast(second)->resource()->data();
+ src = ExternalAsciiString::cast(second)->GetChars();
} else {
src = SeqAsciiString::cast(second)->GetChars();
}
#include "jsregexp.h"
#include "regexp-macro-assembler.h"
#include "stub-cache.h"
+#include "codegen.h"
namespace v8 {
namespace internal {
// StringCharCodeAtGenerator
void StringCharCodeAtGenerator::GenerateFast(MacroAssembler* masm) {
- Label flat_string;
- Label ascii_string;
- Label got_char_code;
- Label sliced_string;
-
// If the receiver is a smi trigger the non-string case.
STATIC_ASSERT(kSmiTag == 0);
__ JumpIfSmi(object_, receiver_not_string_);
__ cmp(index_, FieldOperand(object_, String::kLengthOffset));
__ j(above_equal, index_out_of_range_);
- // We need special handling for non-flat strings.
- STATIC_ASSERT(kSeqStringTag == 0);
- __ test(result_, Immediate(kStringRepresentationMask));
- __ j(zero, &flat_string);
+ __ SmiUntag(index_);
- // Handle non-flat strings.
- __ and_(result_, kStringRepresentationMask);
- STATIC_ASSERT(kConsStringTag < kExternalStringTag);
- STATIC_ASSERT(kSlicedStringTag > kExternalStringTag);
- __ cmp(result_, kExternalStringTag);
- __ j(greater, &sliced_string, Label::kNear);
- __ j(equal, &call_runtime_);
-
- // ConsString.
- // Check whether the right hand side is the empty string (i.e. if
- // this is really a flat string in a cons string). If that is not
- // the case we would rather go to the runtime system now to flatten
- // the string.
- Label assure_seq_string;
- __ cmp(FieldOperand(object_, ConsString::kSecondOffset),
- Immediate(masm->isolate()->factory()->empty_string()));
- __ j(not_equal, &call_runtime_);
- // Get the first of the two parts.
- __ mov(object_, FieldOperand(object_, ConsString::kFirstOffset));
- __ jmp(&assure_seq_string, Label::kNear);
-
- // SlicedString, unpack and add offset.
- __ bind(&sliced_string);
- __ add(index_, FieldOperand(object_, SlicedString::kOffsetOffset));
- __ mov(object_, FieldOperand(object_, SlicedString::kParentOffset));
-
- // Assure that we are dealing with a sequential string. Go to runtime if not.
- // Note that if the original string is a cons or slice with an external
- // string as underlying string, we pass that unpacked underlying string with
- // the adjusted index to the runtime function.
- __ bind(&assure_seq_string);
- __ mov(result_, FieldOperand(object_, HeapObject::kMapOffset));
- __ movzx_b(result_, FieldOperand(result_, Map::kInstanceTypeOffset));
- STATIC_ASSERT(kSeqStringTag == 0);
- __ test(result_, Immediate(kStringRepresentationMask));
- __ j(not_zero, &call_runtime_);
+ Factory* factory = masm->isolate()->factory();
+ StringCharLoadGenerator::Generate(
+ masm, factory, object_, index_, result_, &call_runtime_);
- // Check for 1-byte or 2-byte string.
- __ bind(&flat_string);
- STATIC_ASSERT((kStringEncodingMask & kAsciiStringTag) != 0);
- STATIC_ASSERT((kStringEncodingMask & kTwoByteStringTag) == 0);
- __ test(result_, Immediate(kStringEncodingMask));
- __ j(not_zero, &ascii_string, Label::kNear);
-
- // 2-byte string.
- // Load the 2-byte character code into the result register.
- STATIC_ASSERT(kSmiTag == 0 && kSmiTagSize == 1);
- __ movzx_w(result_, FieldOperand(object_,
- index_, times_1, // Scratch is smi-tagged.
- SeqTwoByteString::kHeaderSize));
- __ jmp(&got_char_code, Label::kNear);
-
- // ASCII string.
- // Load the byte into the result register.
- __ bind(&ascii_string);
- __ SmiUntag(index_);
- __ movzx_b(result_, FieldOperand(object_,
- index_, times_1,
- SeqAsciiString::kHeaderSize));
- __ bind(&got_char_code);
__ SmiTag(result_);
__ bind(&exit_);
}
__ bind(&call_runtime_);
call_helper.BeforeCall(masm);
__ push(object_);
+ __ SmiTag(index_);
__ push(index_);
__ CallRuntime(Runtime::kStringCharCodeAt, 2);
if (!result_.is(eax)) {
__ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset));
}
+
+void StringCharLoadGenerator::Generate(MacroAssembler* masm,
+ Factory* factory,
+ Register string,
+ Register index,
+ Register result,
+ Label* call_runtime) {
+ // Fetch the instance type of the receiver into result register.
+ __ mov(result, FieldOperand(string, HeapObject::kMapOffset));
+ __ movzx_b(result, FieldOperand(result, Map::kInstanceTypeOffset));
+
+ // We need special handling for indirect strings.
+ Label check_sequential;
+ __ test(result, Immediate(kIsIndirectStringMask));
+ __ j(zero, &check_sequential);
+
+ // Dispatch on the indirect string shape: slice or cons.
+ Label cons_string;
+ __ test(result, Immediate(kSlicedNotConsMask));
+ __ j(zero, &cons_string);
+
+ // Handle slices.
+ Label indirect_string_loaded;
+ __ mov(result, FieldOperand(string, SlicedString::kOffsetOffset));
+ __ SmiUntag(result);
+ __ add(index, result);
+ __ mov(string, FieldOperand(string, SlicedString::kParentOffset));
+ __ jmp(&indirect_string_loaded);
+
+ // Handle external strings.
+ Label external_string, ascii_external, done;
+ __ bind(&external_string);
+ if (FLAG_debug_code) {
+ // Assert that we do not have a cons or slice (indirect strings) here.
+ // Sequential strings have already been ruled out.
+ __ test(result, Immediate(kIsIndirectStringMask));
+ __ Assert(zero, "external string expected, but not found");
+ }
+ __ mov(result, FieldOperand(string, ExternalString::kResourceDataOffset));
+ // Assert that the external string has not been finalized yet.
+ __ test(result, result);
+ __ j(zero, call_runtime);
+ Register scratch = string;
+ __ mov(scratch, FieldOperand(string, HeapObject::kMapOffset));
+ __ cmp(scratch, Immediate(factory->external_ascii_string_map()));
+ __ j(equal, &ascii_external, Label::kNear);
+ __ cmp(scratch, Immediate(factory->external_ascii_symbol_map()));
+ __ j(equal, &ascii_external, Label::kNear);
+ // Two-byte string.
+ __ movzx_w(result, Operand(result, index, times_2, 0));
+ __ jmp(&done);
+ __ bind(&ascii_external);
+ // Ascii string.
+ __ movzx_b(result, Operand(result, index, times_1, 0));
+ __ jmp(&done);
+
+ // Handle conses.
+ // Check whether the right hand side is the empty string (i.e. if
+ // this is really a flat string in a cons string). If that is not
+ // the case we would rather go to the runtime system now to flatten
+ // the string.
+ __ bind(&cons_string);
+ __ cmp(FieldOperand(string, ConsString::kSecondOffset),
+ Immediate(factory->empty_string()));
+ __ j(not_equal, call_runtime);
+ __ mov(string, FieldOperand(string, ConsString::kFirstOffset));
+
+ __ bind(&indirect_string_loaded);
+ __ mov(result, FieldOperand(string, HeapObject::kMapOffset));
+ __ movzx_b(result, FieldOperand(result, Map::kInstanceTypeOffset));
+
+ // Check whether the string is sequential. The only non-sequential
+ // shapes we support have just been unwrapped above.
+ // Note that if the original string is a cons or slice with an external
+ // string as underlying string, we pass that unpacked underlying string with
+ // the adjusted index to the runtime function.
+ __ bind(&check_sequential);
+ STATIC_ASSERT(kSeqStringTag == 0);
+ __ test(result, Immediate(kStringRepresentationMask));
+ __ j(not_zero, &external_string);
+
+ // Dispatch on the encoding: ASCII or two-byte.
+ Label ascii_string;
+ STATIC_ASSERT((kStringEncodingMask & kAsciiStringTag) != 0);
+ STATIC_ASSERT((kStringEncodingMask & kTwoByteStringTag) == 0);
+ __ test(result, Immediate(kStringEncodingMask));
+ __ j(not_zero, &ascii_string, Label::kNear);
+
+ // Two-byte string.
+ // Load the two-byte character code into the result register.
+ STATIC_ASSERT(kSmiTag == 0 && kSmiTagSize == 1);
+ __ movzx_w(result, FieldOperand(string,
+ index,
+ times_2,
+ SeqTwoByteString::kHeaderSize));
+ __ jmp(&done, Label::kNear);
+
+ // Ascii string.
+ // Load the byte into the result register.
+ __ bind(&ascii_string);
+ __ movzx_b(result, FieldOperand(string,
+ index,
+ times_1,
+ SeqAsciiString::kHeaderSize));
+ __ bind(&done);
+}
+
#undef __
} } // namespace v8::internal
#include "code-stubs.h"
#include "deoptimizer.h"
#include "stub-cache.h"
+#include "codegen.h"
namespace v8 {
namespace internal {
LStringCharCodeAt* instr_;
};
- Register string = ToRegister(instr->string());
- Register index = ToRegister(instr->index());
- Register result = ToRegister(instr->result());
-
DeferredStringCharCodeAt* deferred =
new DeferredStringCharCodeAt(this, instr);
- // Fetch the instance type of the receiver into result register.
- __ mov(result, FieldOperand(string, HeapObject::kMapOffset));
- __ movzx_b(result, FieldOperand(result, Map::kInstanceTypeOffset));
-
- // We need special handling for indirect strings.
- Label check_sequential;
- __ test(result, Immediate(kIsIndirectStringMask));
- __ j(zero, &check_sequential, Label::kNear);
-
- // Dispatch on the indirect string shape: slice or cons.
- Label cons_string;
- __ test(result, Immediate(kSlicedNotConsMask));
- __ j(zero, &cons_string, Label::kNear);
-
- // Handle slices.
- Label indirect_string_loaded;
- __ mov(result, FieldOperand(string, SlicedString::kOffsetOffset));
- __ SmiUntag(result);
- __ add(index, Operand(result));
- __ mov(string, FieldOperand(string, SlicedString::kParentOffset));
- __ jmp(&indirect_string_loaded, Label::kNear);
-
- // Handle conses.
- // Check whether the right hand side is the empty string (i.e. if
- // this is really a flat string in a cons string). If that is not
- // the case we would rather go to the runtime system now to flatten
- // the string.
- __ bind(&cons_string);
- __ cmp(FieldOperand(string, ConsString::kSecondOffset),
- Immediate(factory()->empty_string()));
- __ j(not_equal, deferred->entry());
- __ mov(string, FieldOperand(string, ConsString::kFirstOffset));
-
- __ bind(&indirect_string_loaded);
- __ mov(result, FieldOperand(string, HeapObject::kMapOffset));
- __ movzx_b(result, FieldOperand(result, Map::kInstanceTypeOffset));
-
- // Check whether the string is sequential. The only non-sequential
- // shapes we support have just been unwrapped above.
- // Note that if the original string is a cons or slice with an external
- // string as underlying string, we pass that unpacked underlying string with
- // the adjusted index to the runtime function.
- __ bind(&check_sequential);
- STATIC_ASSERT(kSeqStringTag == 0);
- __ test(result, Immediate(kStringRepresentationMask));
- __ j(not_zero, deferred->entry());
-
- // Dispatch on the encoding: ASCII or two-byte.
- Label ascii_string;
- STATIC_ASSERT((kStringEncodingMask & kAsciiStringTag) != 0);
- STATIC_ASSERT((kStringEncodingMask & kTwoByteStringTag) == 0);
- __ test(result, Immediate(kStringEncodingMask));
- __ j(not_zero, &ascii_string, Label::kNear);
-
- // Two-byte string.
- // Load the two-byte character code into the result register.
- Label done;
- STATIC_ASSERT(kSmiTag == 0 && kSmiTagSize == 1);
- __ movzx_w(result, FieldOperand(string,
- index,
- times_2,
- SeqTwoByteString::kHeaderSize));
- __ jmp(&done, Label::kNear);
-
- // ASCII string.
- // Load the byte into the result register.
- __ bind(&ascii_string);
- __ movzx_b(result, FieldOperand(string,
- index,
- times_1,
- SeqAsciiString::kHeaderSize));
- __ bind(&done);
+ StringCharLoadGenerator::Generate(masm(),
+ factory(),
+ ToRegister(instr->string()),
+ ToRegister(instr->index()),
+ ToRegister(instr->result()),
+ deferred->entry());
__ bind(deferred->exit());
}
}
+void ExternalString::clear_data_cache() {
+ WRITE_INTPTR_FIELD(this, kResourceDataOffset, NULL);
+}
+
+
const ExternalAsciiString::Resource* ExternalAsciiString::resource() {
return *reinterpret_cast<Resource**>(FIELD_ADDR(this, kResourceOffset));
}
const ExternalAsciiString::Resource* resource) {
*reinterpret_cast<const Resource**>(
FIELD_ADDR(this, kResourceOffset)) = resource;
+ clear_data_cache();
+}
+
+
+const char* ExternalAsciiString::GetChars() {
+ const char** data_field =
+ reinterpret_cast<const char**>(FIELD_ADDR(this, kResourceDataOffset));
+ if (*data_field == NULL) *data_field = resource()->data();
+ return *data_field;
+}
+
+
+uint16_t ExternalAsciiString::ExternalAsciiStringGet(int index) {
+ ASSERT(index >= 0 && index < length());
+ return GetChars()[index];
}
const ExternalTwoByteString::Resource* resource) {
*reinterpret_cast<const Resource**>(
FIELD_ADDR(this, kResourceOffset)) = resource;
+ clear_data_cache();
+}
+
+
+const uint16_t* ExternalTwoByteString::GetChars() {
+ const uint16_t** data_field =
+ reinterpret_cast<const uint16_t**>(FIELD_ADDR(this, kResourceDataOffset));
+ if (*data_field == NULL) *data_field = resource()->data();
+ return *data_field;
+}
+
+
+uint16_t ExternalTwoByteString::ExternalTwoByteStringGet(int index) {
+ ASSERT(index >= 0 && index < length());
+ return GetChars()[index];
+}
+
+
+const uint16_t* ExternalTwoByteString::ExternalTwoByteStringGetData(
+ unsigned start) {
+ return GetChars() + start;
}
Heap* heap = GetHeap();
int size = this->Size(); // Byte size of the original string.
if (size < ExternalString::kSize) {
- // The string is too small to fit an external String in its place. This can
- // only happen for zero length strings.
return false;
}
ASSERT(size >= ExternalString::kSize);
Heap* heap = GetHeap();
int size = this->Size(); // Byte size of the original string.
if (size < ExternalString::kSize) {
- // The string is too small to fit an external String in its place. This can
- // only happen for zero length strings.
return false;
}
ASSERT(size >= ExternalString::kSize);
if (shape.representation_tag() == kSeqStringTag) {
start = SeqAsciiString::cast(string)->GetChars();
} else {
- start = ExternalAsciiString::cast(string)->resource()->data();
+ start = ExternalAsciiString::cast(string)->GetChars();
}
return FlatContent(Vector<const char>(start + offset, length));
} else {
if (shape.representation_tag() == kSeqStringTag) {
start = SeqTwoByteString::cast(string)->GetChars();
} else {
- start = ExternalTwoByteString::cast(string)->resource()->data();
+ start = ExternalTwoByteString::cast(string)->GetChars();
}
return FlatContent(Vector<const uc16>(start + offset, length));
}
}
-uint16_t ExternalAsciiString::ExternalAsciiStringGet(int index) {
- ASSERT(index >= 0 && index < length());
- return resource()->data()[index];
-}
-
-
const unibrow::byte* ExternalAsciiString::ExternalAsciiStringReadBlock(
unsigned* remaining,
unsigned* offset_ptr,
unsigned max_chars) {
// Cast const char* to unibrow::byte* (signedness difference).
const unibrow::byte* b =
- reinterpret_cast<const unibrow::byte*>(resource()->data()) + *offset_ptr;
+ reinterpret_cast<const unibrow::byte*>(GetChars()) + *offset_ptr;
*remaining = max_chars;
*offset_ptr += max_chars;
return b;
}
-const uc16* ExternalTwoByteString::ExternalTwoByteStringGetData(
- unsigned start) {
- return resource()->data() + start;
-}
-
-
-uint16_t ExternalTwoByteString::ExternalTwoByteStringGet(int index) {
- ASSERT(index >= 0 && index < length());
- return resource()->data()[index];
-}
-
-
void ExternalTwoByteString::ExternalTwoByteStringReadBlockIntoBuffer(
ReadBlockBuffer* rbb,
unsigned* offset_ptr,
unsigned max_chars) {
unsigned chars_read = 0;
unsigned offset = *offset_ptr;
- const uint16_t* data = resource()->data();
+ const uint16_t* data = GetChars();
while (chars_read < max_chars) {
uint16_t c = data[offset];
if (c <= kMaxAsciiCharCode) {
unsigned max_chars) {
unsigned capacity = rbb->capacity - rbb->cursor;
if (max_chars > capacity) max_chars = capacity;
- memcpy(rbb->util_buffer + rbb->cursor,
- resource()->data() + *offset_ptr,
- max_chars);
+ memcpy(rbb->util_buffer + rbb->cursor, GetChars() + *offset_ptr, max_chars);
rbb->remaining += max_chars;
*offset_ptr += max_chars;
rbb->cursor += max_chars;
switch (StringShape(source).full_representation_tag()) {
case kAsciiStringTag | kExternalStringTag: {
CopyChars(sink,
- ExternalAsciiString::cast(source)->resource()->data() + from,
+ ExternalAsciiString::cast(source)->GetChars() + from,
to - from);
return;
}
case kTwoByteStringTag | kExternalStringTag: {
const uc16* data =
- ExternalTwoByteString::cast(source)->resource()->data();
+ ExternalTwoByteString::cast(source)->GetChars();
CopyChars(sink,
data + from,
to - from);
// Layout description.
static const int kResourceOffset = POINTER_SIZE_ALIGN(String::kSize);
- static const int kSize = kResourceOffset + kPointerSize;
+ static const int kResourceDataOffset = kResourceOffset + kPointerSize;
+ static const int kSize = kResourceDataOffset + kPointerSize;
+
+ // Clear the cached pointer to the character array provided by the resource.
+ // This cache is updated the first time the character array is accessed.
+ inline void clear_data_cache();
STATIC_CHECK(kResourceOffset == Internals::kStringResourceOffset);
inline const Resource* resource();
inline void set_resource(const Resource* buffer);
+ inline const char* GetChars();
+
// Dispatched behavior.
- uint16_t ExternalAsciiStringGet(int index);
+ inline uint16_t ExternalAsciiStringGet(int index);
// Casting.
static inline ExternalAsciiString* cast(Object* obj);
inline const Resource* resource();
inline void set_resource(const Resource* buffer);
+ inline const uint16_t* GetChars();
+
// Dispatched behavior.
- uint16_t ExternalTwoByteStringGet(int index);
+ inline uint16_t ExternalTwoByteStringGet(int index);
// For regexp code.
- const uint16_t* ExternalTwoByteStringGetData(unsigned start);
+ inline const uint16_t* ExternalTwoByteStringGetData(unsigned start);
// Casting.
static inline ExternalTwoByteString* cast(Object* obj);
if (subject->IsAsciiRepresentation()) {
const byte* address;
if (StringShape(subject).IsExternal()) {
- const char* data = ExternalAsciiString::cast(subject)->resource()->data();
+ const char* data = ExternalAsciiString::cast(subject)->GetChars();
address = reinterpret_cast<const byte*>(data);
} else {
ASSERT(subject->IsSeqAsciiString());
}
const uc16* data;
if (StringShape(subject).IsExternal()) {
- data = ExternalTwoByteString::cast(subject)->resource()->data();
+ data = ExternalTwoByteString::cast(subject)->GetChars();
} else {
ASSERT(subject->IsSeqTwoByteString());
data = SeqTwoByteString::cast(subject)->GetChars();
sink_->Put(kNativesStringResource, "NativesStringResource");
sink_->PutSection(i, "NativesStringResourceEnd");
bytes_processed_so_far_ += sizeof(resource);
+ string->clear_data_cache();
return;
}
}
HEAP->CollectGarbage(i::NEW_SPACE);
HEAP->CollectGarbage(i::NEW_SPACE);
- uint16_t* two_byte_string = AsciiToTwoByteString("small");
+ uint16_t* two_byte_string = AsciiToTwoByteString("abcdefghi");
Local<String> small_string = String::New(two_byte_string);
i::DeleteArray(two_byte_string);
// Old space strings should be accepted.
CHECK(small_string->CanMakeExternal());
- two_byte_string = AsciiToTwoByteString("small 2");
+ two_byte_string = AsciiToTwoByteString("abcdefghi");
small_string = String::New(two_byte_string);
i::DeleteArray(two_byte_string);
HEAP->CollectGarbage(i::NEW_SPACE);
HEAP->CollectGarbage(i::NEW_SPACE);
- Local<String> small_string = String::New("small");
+ Local<String> small_string = String::New("abcdefghi");
// We should refuse to externalize newly created small string.
CHECK(!small_string->CanMakeExternal());
// Trigger GCs so that the newly allocated string moves to old gen.
// Old space strings should be accepted.
CHECK(small_string->CanMakeExternal());
- small_string = String::New("small 2");
+ small_string = String::New("abcdefghi");
// We should refuse externalizing newly created small string.
CHECK(!small_string->CanMakeExternal());
for (int i = 0; i < 100; i++) {
assertTrue(isAsciiString(str));
var twoByteExternalWithAsciiData =
- "AA" + (function() { return "A"; })();
+ "AAAAAAAA" + (function() { return "A"; })();
externalizeString(twoByteExternalWithAsciiData, true /* force two-byte */);
assertFalse(isAsciiString(twoByteExternalWithAsciiData));
var realTwoByteExternalString =
- "\u1234\u1234" + (function() { return "\u1234"; })();
+ "\u1234\u1234\u1234\u1234" + (function() { return "\u1234"; })();
externalizeString(realTwoByteExternalString);
assertFalse(isAsciiString(realTwoByteExternalString));
// Flattened string should still be two-byte.
assertFalse(isAsciiString(str2));
+
+ // Test buffered external strings.
+ var charat_str = new Array(5);
+ charat_str[0] = "0123456789ABCDEF0123456789ABCDEF\
+0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF\
+0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF\
+0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF\
+0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
+ charat_str[1] = "0123456789ABCDEF";
+ for (var i = 0; i < 6; i++) charat_str[1] += charat_str[1];
+ try { // String can only be externalized once
+ externalizeString(charat_str[0], false);
+ externalizeString(charat_str[1], true);
+ } catch (ex) { }
+ charat_str[2] = charat_str[0].slice(0, -1);
+ charat_str[3] = charat_str[1].slice(0, -1);
+ charat_str[4] = charat_str[0] + charat_str[0];
+
+ for (var i = 0; i < 5; i++) {
+ assertEquals('B', charat_str[i].charAt(6*16 + 11));
+ assertEquals('C', charat_str[i].charAt(6*16 + 12));
+ assertEquals('A', charat_str[i].charAt(3*16 + 10));
+ assertEquals('B', charat_str[i].charAt(3*16 + 11));
+ }
}
// Run the test many times to ensure IC-s don't break things.