Add initial hydrogenized NewStringAddStub.
The new stub is enabled via the --new-string-add flag, which is disabled by default. For now, it's only a stripped down version of the native StringAddStub, it's still work-in-progress. BUG=v8:2990 R=mvstanton@chromium.org Review URL: https://codereview.chromium.org/61893009 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@17635 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
51d637a073
commit
6f75e92902
@ -302,6 +302,17 @@ void ElementsTransitionAndStoreStub::InitializeInterfaceDescriptor(
|
||||
}
|
||||
|
||||
|
||||
void NewStringAddStub::InitializeInterfaceDescriptor(
|
||||
Isolate* isolate,
|
||||
CodeStubInterfaceDescriptor* descriptor) {
|
||||
static Register registers[] = { r1, r0 };
|
||||
descriptor->register_param_count_ = 2;
|
||||
descriptor->register_params_ = registers;
|
||||
descriptor->deoptimization_handler_ =
|
||||
Runtime::FunctionForId(Runtime::kStringAdd)->entry;
|
||||
}
|
||||
|
||||
|
||||
#define __ ACCESS_MASM(masm)
|
||||
|
||||
|
||||
|
@ -3705,11 +3705,21 @@ void FullCodeGenerator::EmitStringCharAt(CallRuntime* expr) {
|
||||
void FullCodeGenerator::EmitStringAdd(CallRuntime* expr) {
|
||||
ZoneList<Expression*>* args = expr->arguments();
|
||||
ASSERT_EQ(2, args->length());
|
||||
VisitForStackValue(args->at(0));
|
||||
VisitForStackValue(args->at(1));
|
||||
|
||||
StringAddStub stub(STRING_ADD_CHECK_BOTH);
|
||||
__ CallStub(&stub);
|
||||
if (FLAG_new_string_add) {
|
||||
VisitForStackValue(args->at(0));
|
||||
VisitForAccumulatorValue(args->at(1));
|
||||
|
||||
__ pop(r1);
|
||||
NewStringAddStub stub(STRING_ADD_CHECK_BOTH, NOT_TENURED);
|
||||
__ CallStub(&stub);
|
||||
} else {
|
||||
VisitForStackValue(args->at(0));
|
||||
VisitForStackValue(args->at(1));
|
||||
|
||||
StringAddStub stub(STRING_ADD_CHECK_BOTH);
|
||||
__ CallStub(&stub);
|
||||
}
|
||||
context()->Plug(r0);
|
||||
}
|
||||
|
||||
|
@ -2413,8 +2413,12 @@ LInstruction* LChunkBuilder::DoStoreNamedGeneric(HStoreNamedGeneric* instr) {
|
||||
|
||||
LInstruction* LChunkBuilder::DoStringAdd(HStringAdd* instr) {
|
||||
LOperand* context = UseFixed(instr->context(), cp);
|
||||
LOperand* left = UseRegisterAtStart(instr->left());
|
||||
LOperand* right = UseRegisterAtStart(instr->right());
|
||||
LOperand* left = FLAG_new_string_add
|
||||
? UseFixed(instr->left(), r1)
|
||||
: UseRegisterAtStart(instr->left());
|
||||
LOperand* right = FLAG_new_string_add
|
||||
? UseFixed(instr->right(), r0)
|
||||
: UseRegisterAtStart(instr->right());
|
||||
return MarkAsCall(
|
||||
DefineFixed(new(zone()) LStringAdd(context, left, right), r0),
|
||||
instr);
|
||||
|
@ -4562,10 +4562,18 @@ void LCodeGen::DoTrapAllocationMemento(LTrapAllocationMemento* instr) {
|
||||
|
||||
void LCodeGen::DoStringAdd(LStringAdd* instr) {
|
||||
ASSERT(ToRegister(instr->context()).is(cp));
|
||||
__ push(ToRegister(instr->left()));
|
||||
__ push(ToRegister(instr->right()));
|
||||
StringAddStub stub(instr->hydrogen()->flags());
|
||||
CallCode(stub.GetCode(isolate()), RelocInfo::CODE_TARGET, instr);
|
||||
if (FLAG_new_string_add) {
|
||||
ASSERT(ToRegister(instr->left()).is(r1));
|
||||
ASSERT(ToRegister(instr->right()).is(r0));
|
||||
NewStringAddStub stub(instr->hydrogen()->flags(),
|
||||
isolate()->heap()->GetPretenureMode());
|
||||
CallCode(stub.GetCode(isolate()), RelocInfo::CODE_TARGET, instr);
|
||||
} else {
|
||||
__ push(ToRegister(instr->left()));
|
||||
__ push(ToRegister(instr->right()));
|
||||
StringAddStub stub(instr->hydrogen()->flags());
|
||||
CallCode(stub.GetCode(isolate()), RelocInfo::CODE_TARGET, instr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -971,6 +971,38 @@ Handle<Code> BinaryOpStub::GenerateCode(Isolate* isolate) {
|
||||
}
|
||||
|
||||
|
||||
template <>
|
||||
HValue* CodeStubGraphBuilder<NewStringAddStub>::BuildCodeInitializedStub() {
|
||||
NewStringAddStub* stub = casted_stub();
|
||||
StringAddFlags flags = stub->flags();
|
||||
PretenureFlag pretenure_flag = stub->pretenure_flag();
|
||||
|
||||
HValue* left = GetParameter(NewStringAddStub::kLeft);
|
||||
HValue* right = GetParameter(NewStringAddStub::kRight);
|
||||
|
||||
// Make sure that both arguments are strings if not known in advance.
|
||||
if ((flags & STRING_ADD_CHECK_LEFT) == STRING_ADD_CHECK_LEFT) {
|
||||
IfBuilder if_leftnotstring(this);
|
||||
if_leftnotstring.IfNot<HIsStringAndBranch>(left);
|
||||
if_leftnotstring.Then();
|
||||
if_leftnotstring.Deopt("Expected string for LHS of string addition");
|
||||
}
|
||||
if ((flags & STRING_ADD_CHECK_RIGHT) == STRING_ADD_CHECK_RIGHT) {
|
||||
IfBuilder if_rightnotstring(this);
|
||||
if_rightnotstring.IfNot<HIsStringAndBranch>(right);
|
||||
if_rightnotstring.Then();
|
||||
if_rightnotstring.Deopt("Expected string for RHS of string addition");
|
||||
}
|
||||
|
||||
return BuildStringAdd(left, right, pretenure_flag);
|
||||
}
|
||||
|
||||
|
||||
Handle<Code> NewStringAddStub::GenerateCode(Isolate* isolate) {
|
||||
return DoGenerateCode(isolate, this);
|
||||
}
|
||||
|
||||
|
||||
template <>
|
||||
HValue* CodeStubGraphBuilder<ToBooleanStub>::BuildCodeInitializedStub() {
|
||||
ToBooleanStub* stub = casted_stub();
|
||||
|
@ -682,6 +682,21 @@ Handle<Type> BinaryOpStub::GetResultType(Isolate* isolate) const {
|
||||
}
|
||||
|
||||
|
||||
void NewStringAddStub::PrintBaseName(StringStream* stream) {
|
||||
stream->Add("NewStringAddStub");
|
||||
if ((flags() & STRING_ADD_CHECK_BOTH) == STRING_ADD_CHECK_BOTH) {
|
||||
stream->Add("_CheckBoth");
|
||||
} else if ((flags() & STRING_ADD_CHECK_LEFT) == STRING_ADD_CHECK_LEFT) {
|
||||
stream->Add("_CheckLeft");
|
||||
} else if ((flags() & STRING_ADD_CHECK_RIGHT) == STRING_ADD_CHECK_RIGHT) {
|
||||
stream->Add("_CheckRight");
|
||||
}
|
||||
if (pretenure_flag() == TENURED) {
|
||||
stream->Add("_Tenured");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
InlineCacheState ICCompareStub::GetICState() {
|
||||
CompareIC::State state = Max(left_, right_);
|
||||
switch (state) {
|
||||
@ -1134,6 +1149,13 @@ void FastNewClosureStub::InstallDescriptors(Isolate* isolate) {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
void NewStringAddStub::InstallDescriptors(Isolate* isolate) {
|
||||
NewStringAddStub stub(STRING_ADD_CHECK_NONE, NOT_TENURED);
|
||||
InstallDescriptor(isolate, &stub);
|
||||
}
|
||||
|
||||
|
||||
ArrayConstructorStub::ArrayConstructorStub(Isolate* isolate)
|
||||
: argument_count_(ANY) {
|
||||
ArrayConstructorStubBase::GenerateStubsAheadOfTime(isolate);
|
||||
|
@ -43,6 +43,7 @@ namespace internal {
|
||||
V(CallConstruct) \
|
||||
V(BinaryOp) \
|
||||
V(StringAdd) \
|
||||
V(NewStringAdd) \
|
||||
V(SubString) \
|
||||
V(StringCompare) \
|
||||
V(Compare) \
|
||||
@ -1179,6 +1180,47 @@ class BinaryOpStub: public HydrogenCodeStub {
|
||||
};
|
||||
|
||||
|
||||
// TODO(bmeurer): Rename to StringAddStub once we dropped the old StringAddStub.
|
||||
class NewStringAddStub V8_FINAL : public HydrogenCodeStub {
|
||||
public:
|
||||
NewStringAddStub(StringAddFlags flags, PretenureFlag pretenure_flag)
|
||||
: bit_field_(StringAddFlagsBits::encode(flags) |
|
||||
PretenureFlagBits::encode(pretenure_flag)) {}
|
||||
|
||||
StringAddFlags flags() const {
|
||||
return StringAddFlagsBits::decode(bit_field_);
|
||||
}
|
||||
|
||||
PretenureFlag pretenure_flag() const {
|
||||
return PretenureFlagBits::decode(bit_field_);
|
||||
}
|
||||
|
||||
virtual Handle<Code> GenerateCode(Isolate* isolate) V8_OVERRIDE;
|
||||
|
||||
virtual void InitializeInterfaceDescriptor(
|
||||
Isolate* isolate,
|
||||
CodeStubInterfaceDescriptor* descriptor) V8_OVERRIDE;
|
||||
|
||||
static void InstallDescriptors(Isolate* isolate);
|
||||
|
||||
// Parameters accessed via CodeStubGraphBuilder::GetParameter()
|
||||
static const int kLeft = 0;
|
||||
static const int kRight = 1;
|
||||
|
||||
private:
|
||||
class StringAddFlagsBits: public BitField<StringAddFlags, 0, 2> {};
|
||||
class PretenureFlagBits: public BitField<PretenureFlag, 2, 1> {};
|
||||
uint32_t bit_field_;
|
||||
|
||||
virtual Major MajorKey() V8_OVERRIDE { return NewStringAdd; }
|
||||
virtual int NotMissMinorKey() V8_OVERRIDE { return bit_field_; }
|
||||
|
||||
virtual void PrintBaseName(StringStream* stream) V8_OVERRIDE;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(NewStringAddStub);
|
||||
};
|
||||
|
||||
|
||||
class ICCompareStub: public PlatformCodeStub {
|
||||
public:
|
||||
ICCompareStub(Token::Value op,
|
||||
|
@ -338,6 +338,8 @@ DEFINE_bool(omit_map_checks_for_leaf_maps, true,
|
||||
"do not emit check maps for constant values that have a leaf map, "
|
||||
"deoptimize the optimized code if the layout of the maps changes.")
|
||||
|
||||
DEFINE_bool(new_string_add, false, "enable new string addition")
|
||||
|
||||
// Experimental profiler changes.
|
||||
DEFINE_bool(experimental_profiler, true, "enable all profiler experiments")
|
||||
DEFINE_bool(watch_ic_patching, false, "profiler considers IC stability")
|
||||
|
@ -5808,6 +5808,12 @@ class HObjectAccess V8_FINAL {
|
||||
FLAG_track_fields ? Representation::Smi() : Representation::Tagged());
|
||||
}
|
||||
|
||||
static HObjectAccess ForStringHashField() {
|
||||
return HObjectAccess(kInobject,
|
||||
String::kHashFieldOffset,
|
||||
Representation::Integer32());
|
||||
}
|
||||
|
||||
static HObjectAccess ForStringLength() {
|
||||
STATIC_ASSERT(String::kMaxLength <= Smi::kMaxValue);
|
||||
return HObjectAccess(
|
||||
@ -5816,6 +5822,14 @@ class HObjectAccess V8_FINAL {
|
||||
FLAG_track_fields ? Representation::Smi() : Representation::Tagged());
|
||||
}
|
||||
|
||||
static HObjectAccess ForConsStringFirst() {
|
||||
return HObjectAccess(kInobject, ConsString::kFirstOffset);
|
||||
}
|
||||
|
||||
static HObjectAccess ForConsStringSecond() {
|
||||
return HObjectAccess(kInobject, ConsString::kSecondOffset);
|
||||
}
|
||||
|
||||
static HObjectAccess ForPropertiesPointer() {
|
||||
return HObjectAccess(kInobject, JSObject::kPropertiesOffset);
|
||||
}
|
||||
@ -5863,6 +5877,12 @@ class HObjectAccess V8_FINAL {
|
||||
Representation::UInteger8());
|
||||
}
|
||||
|
||||
static HObjectAccess ForMapInstanceType() {
|
||||
return HObjectAccess(kInobject,
|
||||
Map::kInstanceTypeOffset,
|
||||
Representation::UInteger8());
|
||||
}
|
||||
|
||||
static HObjectAccess ForPropertyCellValue() {
|
||||
return HObjectAccess(kInobject, PropertyCell::kValueOffset);
|
||||
}
|
||||
|
336
src/hydrogen.cc
336
src/hydrogen.cc
@ -1469,6 +1469,342 @@ HValue* HGraphBuilder::BuildNumberToString(HValue* object,
|
||||
}
|
||||
|
||||
|
||||
HValue* HGraphBuilder::BuildSeqStringSizeFor(HValue* length,
|
||||
String::Encoding encoding) {
|
||||
STATIC_ASSERT((SeqString::kHeaderSize & kObjectAlignmentMask) == 0);
|
||||
HValue* size = length;
|
||||
if (encoding == String::TWO_BYTE_ENCODING) {
|
||||
size = Add<HShl>(length, graph()->GetConstant1());
|
||||
size->ClearFlag(HValue::kCanOverflow);
|
||||
size->SetFlag(HValue::kUint32);
|
||||
}
|
||||
size = Add<HAdd>(size, Add<HConstant>(static_cast<int32_t>(
|
||||
SeqString::kHeaderSize + kObjectAlignmentMask)));
|
||||
size->ClearFlag(HValue::kCanOverflow);
|
||||
size = Add<HBitwise>(
|
||||
Token::BIT_AND, size, Add<HConstant>(static_cast<int32_t>(
|
||||
~kObjectAlignmentMask)));
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
void HGraphBuilder::BuildCopySeqStringChars(HValue* src,
|
||||
HValue* src_offset,
|
||||
String::Encoding src_encoding,
|
||||
HValue* dst,
|
||||
HValue* dst_offset,
|
||||
String::Encoding dst_encoding,
|
||||
HValue* length) {
|
||||
ASSERT(dst_encoding != String::ONE_BYTE_ENCODING ||
|
||||
src_encoding == String::ONE_BYTE_ENCODING);
|
||||
LoopBuilder loop(this, context(), LoopBuilder::kPostIncrement);
|
||||
HValue* index = loop.BeginBody(graph()->GetConstant0(), length, Token::LT);
|
||||
{
|
||||
HValue* src_index = Add<HAdd>(src_offset, index);
|
||||
HValue* value = Add<HSeqStringGetChar>(src_encoding, src, src_index);
|
||||
HValue* dst_index = Add<HAdd>(dst_offset, index);
|
||||
Add<HSeqStringSetChar>(dst_encoding, dst, dst_index, value);
|
||||
}
|
||||
loop.EndBody();
|
||||
}
|
||||
|
||||
|
||||
HValue* HGraphBuilder::BuildUncheckedStringAdd(HValue* left,
|
||||
HValue* right,
|
||||
PretenureFlag pretenure_flag) {
|
||||
// Determine the string lengths.
|
||||
HValue* left_length = Add<HLoadNamedField>(
|
||||
left, HObjectAccess::ForStringLength());
|
||||
HValue* right_length = Add<HLoadNamedField>(
|
||||
right, HObjectAccess::ForStringLength());
|
||||
|
||||
// Check if we concatenated the strings here, or if we have to resort to the
|
||||
// runtime function.
|
||||
HIfContinuation handled(graph()->CreateBasicBlock(),
|
||||
graph()->CreateBasicBlock());
|
||||
|
||||
// Check if both parameters do not exceed half the max string length, because
|
||||
// exceptionally long strings should be handled in the runtime. Unfortunately
|
||||
// we cannot actually check whether the combined length of both strings
|
||||
// exceeds String::kMaxLength (because of unclear results from the
|
||||
// representation inference phase), so we use a pessimistic approach here
|
||||
// instead, checking that the length of either substring does not exceed half
|
||||
// of String::kMaxLength.
|
||||
HConstant* max_length = Add<HConstant>(String::kMaxLength / 2);
|
||||
IfBuilder if_nooverflow(this);
|
||||
if_nooverflow.If<HCompareNumericAndBranch>(
|
||||
left_length, max_length, Token::LTE);
|
||||
if_nooverflow.AndIf<HCompareNumericAndBranch>(
|
||||
right_length, max_length, Token::LTE);
|
||||
if_nooverflow.Then();
|
||||
{
|
||||
// Determine the string instance types.
|
||||
HLoadNamedField* left_instance_type = Add<HLoadNamedField>(
|
||||
Add<HLoadNamedField>(left, HObjectAccess::ForMap()),
|
||||
HObjectAccess::ForMapInstanceType());
|
||||
HLoadNamedField* right_instance_type = Add<HLoadNamedField>(
|
||||
Add<HLoadNamedField>(right, HObjectAccess::ForMap()),
|
||||
HObjectAccess::ForMapInstanceType());
|
||||
|
||||
// Compute difference of instance types.
|
||||
HValue* xored_instance_types = Add<HBitwise>(
|
||||
Token::BIT_XOR, left_instance_type, right_instance_type);
|
||||
|
||||
// Compute the length of the resulting string.
|
||||
HValue* length = Add<HAdd>(left_length, right_length);
|
||||
|
||||
// Check if we should create a cons string.
|
||||
IfBuilder if_createcons(this);
|
||||
if_createcons.If<HCompareNumericAndBranch>(
|
||||
length, Add<HConstant>(ConsString::kMinLength), Token::GTE);
|
||||
if_createcons.Then();
|
||||
{
|
||||
// Allocate the cons string object. HAllocate does not care whether we
|
||||
// pass CONS_STRING_TYPE or CONS_ASCII_STRING_TYPE here, so we just use
|
||||
// CONS_STRING_TYPE here. Below we decide whether the cons string is
|
||||
// one-byte or two-byte and set the appropriate map.
|
||||
HAllocate* string = Add<HAllocate>(Add<HConstant>(ConsString::kSize),
|
||||
HType::String(), pretenure_flag,
|
||||
CONS_STRING_TYPE);
|
||||
|
||||
// Compute the intersection of instance types.
|
||||
HValue* anded_instance_types = Add<HBitwise>(
|
||||
Token::BIT_AND, left_instance_type, right_instance_type);
|
||||
|
||||
// We create a one-byte cons string if
|
||||
// 1. both strings are one-byte, or
|
||||
// 2. at least one of the strings is two-byte, but happens to contain only
|
||||
// one-byte characters.
|
||||
// To do this, we check
|
||||
// 1. if both strings are one-byte, or if the one-byte data hint is set in
|
||||
// both strings, or
|
||||
// 2. if one of the strings has the one-byte data hint set and the other
|
||||
// string is one-byte.
|
||||
IfBuilder if_onebyte(this);
|
||||
STATIC_ASSERT(kOneByteStringTag != 0);
|
||||
STATIC_ASSERT(kOneByteDataHintMask != 0);
|
||||
if_onebyte.If<HCompareNumericAndBranch>(
|
||||
Add<HBitwise>(
|
||||
Token::BIT_AND, anded_instance_types,
|
||||
Add<HConstant>(static_cast<int32_t>(
|
||||
kStringEncodingMask | kOneByteDataHintMask))),
|
||||
graph()->GetConstant0(), Token::NE);
|
||||
if_onebyte.Or();
|
||||
STATIC_ASSERT(kOneByteStringTag != 0 &&
|
||||
kOneByteDataHintTag != 0 &&
|
||||
kOneByteDataHintTag != kOneByteStringTag);
|
||||
if_onebyte.If<HCompareNumericAndBranch>(
|
||||
Add<HBitwise>(
|
||||
Token::BIT_AND, xored_instance_types,
|
||||
Add<HConstant>(static_cast<int32_t>(
|
||||
kOneByteStringTag | kOneByteDataHintTag))),
|
||||
Add<HConstant>(static_cast<int32_t>(
|
||||
kOneByteStringTag | kOneByteDataHintTag)), Token::EQ);
|
||||
if_onebyte.Then();
|
||||
{
|
||||
// We can safely skip the write barrier for storing the map here.
|
||||
Handle<Map> map = isolate()->factory()->cons_ascii_string_map();
|
||||
AddStoreMapConstantNoWriteBarrier(string, map);
|
||||
}
|
||||
if_onebyte.Else();
|
||||
{
|
||||
// We can safely skip the write barrier for storing the map here.
|
||||
Handle<Map> map = isolate()->factory()->cons_string_map();
|
||||
AddStoreMapConstantNoWriteBarrier(string, map);
|
||||
}
|
||||
if_onebyte.End();
|
||||
|
||||
// Initialize the cons string fields.
|
||||
Add<HStoreNamedField>(string, HObjectAccess::ForStringHashField(),
|
||||
Add<HConstant>(String::kEmptyHashField));
|
||||
Add<HStoreNamedField>(string, HObjectAccess::ForStringLength(), length);
|
||||
Add<HStoreNamedField>(string, HObjectAccess::ForConsStringFirst(), left);
|
||||
Add<HStoreNamedField>(string, HObjectAccess::ForConsStringSecond(),
|
||||
right);
|
||||
|
||||
// Cons string is result.
|
||||
Push(string);
|
||||
}
|
||||
if_createcons.Else();
|
||||
{
|
||||
// Compute union of instance types.
|
||||
HValue* ored_instance_types = Add<HBitwise>(
|
||||
Token::BIT_OR, left_instance_type, right_instance_type);
|
||||
|
||||
// Check if both strings have the same encoding and both are
|
||||
// sequential.
|
||||
IfBuilder if_sameencodingandsequential(this);
|
||||
if_sameencodingandsequential.If<HCompareNumericAndBranch>(
|
||||
Add<HBitwise>(
|
||||
Token::BIT_AND, xored_instance_types,
|
||||
Add<HConstant>(static_cast<int32_t>(kStringEncodingMask))),
|
||||
graph()->GetConstant0(), Token::EQ);
|
||||
if_sameencodingandsequential.And();
|
||||
STATIC_ASSERT(kSeqStringTag == 0);
|
||||
if_sameencodingandsequential.If<HCompareNumericAndBranch>(
|
||||
Add<HBitwise>(
|
||||
Token::BIT_AND, ored_instance_types,
|
||||
Add<HConstant>(static_cast<int32_t>(kStringRepresentationMask))),
|
||||
graph()->GetConstant0(), Token::EQ);
|
||||
if_sameencodingandsequential.Then();
|
||||
{
|
||||
// Check if the result is a one-byte string.
|
||||
IfBuilder if_onebyte(this);
|
||||
STATIC_ASSERT(kOneByteStringTag != 0);
|
||||
if_onebyte.If<HCompareNumericAndBranch>(
|
||||
Add<HBitwise>(
|
||||
Token::BIT_AND, ored_instance_types,
|
||||
Add<HConstant>(static_cast<int32_t>(kStringEncodingMask))),
|
||||
graph()->GetConstant0(), Token::NE);
|
||||
if_onebyte.Then();
|
||||
{
|
||||
// Calculate the number of bytes needed for the characters in the
|
||||
// string while observing object alignment.
|
||||
HValue* size = BuildSeqStringSizeFor(
|
||||
length, String::ONE_BYTE_ENCODING);
|
||||
|
||||
// Allocate the ASCII string object.
|
||||
Handle<Map> map = isolate()->factory()->ascii_string_map();
|
||||
HAllocate* string = Add<HAllocate>(size, HType::String(),
|
||||
pretenure_flag, ASCII_STRING_TYPE);
|
||||
string->set_known_initial_map(map);
|
||||
|
||||
// We can safely skip the write barrier for storing map here.
|
||||
AddStoreMapConstantNoWriteBarrier(string, map);
|
||||
|
||||
// Copy bytes from the left string.
|
||||
BuildCopySeqStringChars(
|
||||
left, graph()->GetConstant0(), String::ONE_BYTE_ENCODING,
|
||||
string, graph()->GetConstant0(), String::ONE_BYTE_ENCODING,
|
||||
left_length);
|
||||
|
||||
// Copy bytes from the right string.
|
||||
BuildCopySeqStringChars(
|
||||
right, graph()->GetConstant0(), String::ONE_BYTE_ENCODING,
|
||||
string, left_length, String::ONE_BYTE_ENCODING,
|
||||
right_length);
|
||||
|
||||
// Return the string.
|
||||
Push(string);
|
||||
}
|
||||
if_onebyte.Else();
|
||||
{
|
||||
// Calculate the number of bytes needed for the characters in the
|
||||
// string while observing object alignment.
|
||||
HValue* size = BuildSeqStringSizeFor(
|
||||
length, String::TWO_BYTE_ENCODING);
|
||||
|
||||
// Allocate the two-byte string object.
|
||||
Handle<Map> map = isolate()->factory()->string_map();
|
||||
HAllocate* string = Add<HAllocate>(size, HType::String(),
|
||||
pretenure_flag, STRING_TYPE);
|
||||
string->set_known_initial_map(map);
|
||||
|
||||
// We can safely skip the write barrier for storing map here.
|
||||
AddStoreMapConstantNoWriteBarrier(string, map);
|
||||
|
||||
// Copy bytes from the left string.
|
||||
BuildCopySeqStringChars(
|
||||
left, graph()->GetConstant0(), String::TWO_BYTE_ENCODING,
|
||||
string, graph()->GetConstant0(), String::TWO_BYTE_ENCODING,
|
||||
left_length);
|
||||
|
||||
// Copy bytes from the right string.
|
||||
BuildCopySeqStringChars(
|
||||
right, graph()->GetConstant0(), String::TWO_BYTE_ENCODING,
|
||||
string, left_length, String::TWO_BYTE_ENCODING,
|
||||
right_length);
|
||||
|
||||
// Return the string.
|
||||
Push(string);
|
||||
}
|
||||
if_onebyte.End();
|
||||
|
||||
// Initialize the (common) string fields.
|
||||
HValue* string = Pop();
|
||||
Add<HStoreNamedField>(string, HObjectAccess::ForStringHashField(),
|
||||
Add<HConstant>(String::kEmptyHashField));
|
||||
Add<HStoreNamedField>(string, HObjectAccess::ForStringLength(),
|
||||
length);
|
||||
Push(string);
|
||||
}
|
||||
if_sameencodingandsequential.JoinContinuation(&handled);
|
||||
}
|
||||
if_createcons.JoinContinuation(&handled);
|
||||
}
|
||||
if_nooverflow.JoinContinuation(&handled);
|
||||
|
||||
// Check if the strings were concatenated successfully, otherwise fallback to
|
||||
// add the strings in the runtime.
|
||||
IfBuilder if_handled(this, &handled);
|
||||
if_handled.Then();
|
||||
{
|
||||
// Count the native string addition.
|
||||
AddIncrementCounter(isolate()->counters()->string_add_native());
|
||||
}
|
||||
if_handled.Else();
|
||||
{
|
||||
// Fallback to the runtime to add the two strings.
|
||||
Add<HPushArgument>(left);
|
||||
Add<HPushArgument>(right);
|
||||
Push(Add<HCallRuntime>(isolate()->factory()->empty_string(),
|
||||
Runtime::FunctionForId(Runtime::kStringAdd),
|
||||
2));
|
||||
}
|
||||
if_handled.End();
|
||||
|
||||
return Pop();
|
||||
}
|
||||
|
||||
|
||||
HValue* HGraphBuilder::BuildStringAdd(HValue* left,
|
||||
HValue* right,
|
||||
PretenureFlag pretenure_flag) {
|
||||
// Determine the string lengths.
|
||||
HValue* left_length = Add<HLoadNamedField>(
|
||||
left, HObjectAccess::ForStringLength());
|
||||
HValue* right_length = Add<HLoadNamedField>(
|
||||
right, HObjectAccess::ForStringLength());
|
||||
|
||||
// Check if left string is empty.
|
||||
IfBuilder if_leftisempty(this);
|
||||
if_leftisempty.If<HCompareNumericAndBranch>(
|
||||
left_length, graph()->GetConstant0(), Token::EQ);
|
||||
if_leftisempty.Then();
|
||||
{
|
||||
// Count the native string addition.
|
||||
AddIncrementCounter(isolate()->counters()->string_add_native());
|
||||
|
||||
// Just return the right string.
|
||||
Push(right);
|
||||
}
|
||||
if_leftisempty.Else();
|
||||
{
|
||||
// Check if right string is empty.
|
||||
IfBuilder if_rightisempty(this);
|
||||
if_rightisempty.If<HCompareNumericAndBranch>(
|
||||
right_length, graph()->GetConstant0(), Token::EQ);
|
||||
if_rightisempty.Then();
|
||||
{
|
||||
// Count the native string addition.
|
||||
AddIncrementCounter(isolate()->counters()->string_add_native());
|
||||
|
||||
// Just return the left string.
|
||||
Push(left);
|
||||
}
|
||||
if_rightisempty.Else();
|
||||
{
|
||||
// Concatenate the two non-empty strings.
|
||||
Push(BuildUncheckedStringAdd(left, right, pretenure_flag));
|
||||
}
|
||||
if_rightisempty.End();
|
||||
}
|
||||
if_leftisempty.End();
|
||||
|
||||
return Pop();
|
||||
}
|
||||
|
||||
|
||||
HInstruction* HGraphBuilder::BuildUncheckedMonomorphicElementAccess(
|
||||
HValue* checked_object,
|
||||
HValue* key,
|
||||
|
@ -1276,6 +1276,26 @@ class HGraphBuilder {
|
||||
|
||||
HValue* BuildNumberToString(HValue* object, Handle<Type> type);
|
||||
|
||||
// Computes the size for a sequential string of the given length and encoding.
|
||||
HValue* BuildSeqStringSizeFor(HValue* length,
|
||||
String::Encoding encoding);
|
||||
// Copies characters from one sequential string to another.
|
||||
void BuildCopySeqStringChars(HValue* src,
|
||||
HValue* src_offset,
|
||||
String::Encoding src_encoding,
|
||||
HValue* dst,
|
||||
HValue* dst_offset,
|
||||
String::Encoding dst_encoding,
|
||||
HValue* length);
|
||||
// Both operands are non-empty strings.
|
||||
HValue* BuildUncheckedStringAdd(HValue* left,
|
||||
HValue* right,
|
||||
PretenureFlag pretenure_flag);
|
||||
// Both operands are strings.
|
||||
HValue* BuildStringAdd(HValue* left,
|
||||
HValue* right,
|
||||
PretenureFlag pretenure_flag);
|
||||
|
||||
HInstruction* BuildUncheckedMonomorphicElementAccess(
|
||||
HValue* checked_object,
|
||||
HValue* key,
|
||||
@ -1298,7 +1318,13 @@ class HGraphBuilder {
|
||||
HLoadNamedField* BuildLoadNamedField(HValue* object, HObjectAccess access);
|
||||
HInstruction* AddLoadNamedField(HValue* object, HObjectAccess access);
|
||||
HInstruction* BuildLoadStringLength(HValue* object, HValue* checked_value);
|
||||
HStoreNamedField* AddStoreMapConstant(HValue* object, Handle<Map>);
|
||||
HStoreNamedField* AddStoreMapConstant(HValue* object, Handle<Map> map);
|
||||
HStoreNamedField* AddStoreMapConstantNoWriteBarrier(HValue* object,
|
||||
Handle<Map> map) {
|
||||
HStoreNamedField* store_map = AddStoreMapConstant(object, map);
|
||||
store_map->SkipWriteBarrier();
|
||||
return store_map;
|
||||
}
|
||||
HLoadNamedField* AddLoadElements(HValue* object);
|
||||
|
||||
bool MatchRotateRight(HValue* left,
|
||||
|
@ -306,6 +306,17 @@ void BinaryOpStub::InitializeInterfaceDescriptor(
|
||||
}
|
||||
|
||||
|
||||
void NewStringAddStub::InitializeInterfaceDescriptor(
|
||||
Isolate* isolate,
|
||||
CodeStubInterfaceDescriptor* descriptor) {
|
||||
static Register registers[] = { edx, eax };
|
||||
descriptor->register_param_count_ = 2;
|
||||
descriptor->register_params_ = registers;
|
||||
descriptor->deoptimization_handler_ =
|
||||
Runtime::FunctionForId(Runtime::kStringAdd)->entry;
|
||||
}
|
||||
|
||||
|
||||
#define __ ACCESS_MASM(masm)
|
||||
|
||||
|
||||
|
@ -3671,11 +3671,20 @@ void FullCodeGenerator::EmitStringAdd(CallRuntime* expr) {
|
||||
ZoneList<Expression*>* args = expr->arguments();
|
||||
ASSERT_EQ(2, args->length());
|
||||
|
||||
VisitForStackValue(args->at(0));
|
||||
VisitForStackValue(args->at(1));
|
||||
if (FLAG_new_string_add) {
|
||||
VisitForStackValue(args->at(0));
|
||||
VisitForAccumulatorValue(args->at(1));
|
||||
|
||||
StringAddStub stub(STRING_ADD_CHECK_BOTH);
|
||||
__ CallStub(&stub);
|
||||
__ pop(edx);
|
||||
NewStringAddStub stub(STRING_ADD_CHECK_BOTH, NOT_TENURED);
|
||||
__ CallStub(&stub);
|
||||
} else {
|
||||
VisitForStackValue(args->at(0));
|
||||
VisitForStackValue(args->at(1));
|
||||
|
||||
StringAddStub stub(STRING_ADD_CHECK_BOTH);
|
||||
__ CallStub(&stub);
|
||||
}
|
||||
context()->Plug(eax);
|
||||
}
|
||||
|
||||
|
@ -4551,6 +4551,10 @@ void LCodeGen::DoStoreNamedField(LStoreNamedField* instr) {
|
||||
if (operand_value->IsRegister()) {
|
||||
Register value = ToRegister(operand_value);
|
||||
__ Store(value, operand, representation);
|
||||
} else if (representation.IsInteger32()) {
|
||||
Immediate immediate = ToImmediate(operand_value, representation);
|
||||
ASSERT(!instr->hydrogen()->NeedsWriteBarrier());
|
||||
__ mov(operand, immediate);
|
||||
} else {
|
||||
Handle<Object> handle_value = ToHandle(operand_value);
|
||||
ASSERT(!instr->hydrogen()->NeedsWriteBarrier());
|
||||
@ -4997,10 +5001,18 @@ void LCodeGen::DoDeferredStringCharFromCode(LStringCharFromCode* instr) {
|
||||
|
||||
void LCodeGen::DoStringAdd(LStringAdd* instr) {
|
||||
ASSERT(ToRegister(instr->context()).is(esi));
|
||||
EmitPushTaggedOperand(instr->left());
|
||||
EmitPushTaggedOperand(instr->right());
|
||||
StringAddStub stub(instr->hydrogen()->flags());
|
||||
CallCode(stub.GetCode(isolate()), RelocInfo::CODE_TARGET, instr);
|
||||
if (FLAG_new_string_add) {
|
||||
ASSERT(ToRegister(instr->left()).is(edx));
|
||||
ASSERT(ToRegister(instr->right()).is(eax));
|
||||
NewStringAddStub stub(instr->hydrogen()->flags(),
|
||||
isolate()->heap()->GetPretenureMode());
|
||||
CallCode(stub.GetCode(isolate()), RelocInfo::CODE_TARGET, instr);
|
||||
} else {
|
||||
EmitPushTaggedOperand(instr->left());
|
||||
EmitPushTaggedOperand(instr->right());
|
||||
StringAddStub stub(instr->hydrogen()->flags());
|
||||
CallCode(stub.GetCode(isolate()), RelocInfo::CODE_TARGET, instr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -2485,8 +2485,12 @@ LInstruction* LChunkBuilder::DoStoreNamedGeneric(HStoreNamedGeneric* instr) {
|
||||
|
||||
LInstruction* LChunkBuilder::DoStringAdd(HStringAdd* instr) {
|
||||
LOperand* context = UseFixed(instr->context(), esi);
|
||||
LOperand* left = UseOrConstantAtStart(instr->left());
|
||||
LOperand* right = UseOrConstantAtStart(instr->right());
|
||||
LOperand* left = FLAG_new_string_add
|
||||
? UseFixed(instr->left(), edx)
|
||||
: UseOrConstantAtStart(instr->left());
|
||||
LOperand* right = FLAG_new_string_add
|
||||
? UseFixed(instr->right(), eax)
|
||||
: UseOrConstantAtStart(instr->right());
|
||||
LStringAdd* string_add = new(zone()) LStringAdd(context, left, right);
|
||||
return MarkAsCall(DefineFixed(string_add, eax), instr);
|
||||
}
|
||||
|
@ -2328,6 +2328,7 @@ bool Isolate::Init(Deserializer* des) {
|
||||
InternalArrayConstructorStubBase::InstallDescriptors(this);
|
||||
FastNewClosureStub::InstallDescriptors(this);
|
||||
NumberToStringStub::InstallDescriptors(this);
|
||||
NewStringAddStub::InstallDescriptors(this);
|
||||
}
|
||||
|
||||
if (FLAG_sweeper_threads > 0) {
|
||||
|
@ -302,6 +302,17 @@ void ElementsTransitionAndStoreStub::InitializeInterfaceDescriptor(
|
||||
}
|
||||
|
||||
|
||||
void NewStringAddStub::InitializeInterfaceDescriptor(
|
||||
Isolate* isolate,
|
||||
CodeStubInterfaceDescriptor* descriptor) {
|
||||
static Register registers[] = { rdx, rax };
|
||||
descriptor->register_param_count_ = 2;
|
||||
descriptor->register_params_ = registers;
|
||||
descriptor->deoptimization_handler_ =
|
||||
Runtime::FunctionForId(Runtime::kStringAdd)->entry;
|
||||
}
|
||||
|
||||
|
||||
#define __ ACCESS_MASM(masm)
|
||||
|
||||
|
||||
|
@ -3628,11 +3628,20 @@ void FullCodeGenerator::EmitStringAdd(CallRuntime* expr) {
|
||||
ZoneList<Expression*>* args = expr->arguments();
|
||||
ASSERT_EQ(2, args->length());
|
||||
|
||||
VisitForStackValue(args->at(0));
|
||||
VisitForStackValue(args->at(1));
|
||||
if (FLAG_new_string_add) {
|
||||
VisitForStackValue(args->at(0));
|
||||
VisitForAccumulatorValue(args->at(1));
|
||||
|
||||
StringAddStub stub(STRING_ADD_CHECK_BOTH);
|
||||
__ CallStub(&stub);
|
||||
__ pop(rdx);
|
||||
NewStringAddStub stub(STRING_ADD_CHECK_BOTH, NOT_TENURED);
|
||||
__ CallStub(&stub);
|
||||
} else {
|
||||
VisitForStackValue(args->at(0));
|
||||
VisitForStackValue(args->at(1));
|
||||
|
||||
StringAddStub stub(STRING_ADD_CHECK_BOTH);
|
||||
__ CallStub(&stub);
|
||||
}
|
||||
context()->Plug(rax);
|
||||
}
|
||||
|
||||
|
@ -4077,6 +4077,10 @@ void LCodeGen::DoStoreNamedField(LStoreNamedField* instr) {
|
||||
if (operand_value->IsRegister()) {
|
||||
Register value = ToRegister(operand_value);
|
||||
__ Store(FieldOperand(write_register, offset), value, representation);
|
||||
} else if (representation.IsInteger32()) {
|
||||
int32_t value = ToInteger32(operand_value);
|
||||
ASSERT(!instr->hydrogen()->NeedsWriteBarrier());
|
||||
__ movl(FieldOperand(write_register, offset), Immediate(value));
|
||||
} else {
|
||||
Handle<Object> handle_value = ToHandle(operand_value);
|
||||
ASSERT(!instr->hydrogen()->NeedsWriteBarrier());
|
||||
@ -4400,10 +4404,18 @@ void LCodeGen::DoTrapAllocationMemento(LTrapAllocationMemento* instr) {
|
||||
|
||||
void LCodeGen::DoStringAdd(LStringAdd* instr) {
|
||||
ASSERT(ToRegister(instr->context()).is(rsi));
|
||||
EmitPushTaggedOperand(instr->left());
|
||||
EmitPushTaggedOperand(instr->right());
|
||||
StringAddStub stub(instr->hydrogen()->flags());
|
||||
CallCode(stub.GetCode(isolate()), RelocInfo::CODE_TARGET, instr);
|
||||
if (FLAG_new_string_add) {
|
||||
ASSERT(ToRegister(instr->left()).is(rdx));
|
||||
ASSERT(ToRegister(instr->right()).is(rax));
|
||||
NewStringAddStub stub(instr->hydrogen()->flags(),
|
||||
isolate()->heap()->GetPretenureMode());
|
||||
CallCode(stub.GetCode(isolate()), RelocInfo::CODE_TARGET, instr);
|
||||
} else {
|
||||
EmitPushTaggedOperand(instr->left());
|
||||
EmitPushTaggedOperand(instr->right());
|
||||
StringAddStub stub(instr->hydrogen()->flags());
|
||||
CallCode(stub.GetCode(isolate()), RelocInfo::CODE_TARGET, instr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -2339,8 +2339,12 @@ LInstruction* LChunkBuilder::DoStoreNamedGeneric(HStoreNamedGeneric* instr) {
|
||||
|
||||
LInstruction* LChunkBuilder::DoStringAdd(HStringAdd* instr) {
|
||||
LOperand* context = UseFixed(instr->context(), rsi);
|
||||
LOperand* left = UseOrConstantAtStart(instr->left());
|
||||
LOperand* right = UseOrConstantAtStart(instr->right());
|
||||
LOperand* left = FLAG_new_string_add
|
||||
? UseFixed(instr->left(), rdx)
|
||||
: UseOrConstantAtStart(instr->left());
|
||||
LOperand* right = FLAG_new_string_add
|
||||
? UseFixed(instr->right(), rax)
|
||||
: UseOrConstantAtStart(instr->right());
|
||||
return MarkAsCall(
|
||||
DefineFixed(new(zone()) LStringAdd(context, left, right), rax), instr);
|
||||
}
|
||||
|
197
test/mjsunit/new-string-add.js
Normal file
197
test/mjsunit/new-string-add.js
Normal file
@ -0,0 +1,197 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
// Flags: --new-string-add
|
||||
|
||||
assertEquals("ab", "a" + "b", "ll");
|
||||
|
||||
assertEquals("12", "1" + "2", "dd");
|
||||
assertEquals("123", "1" + "2" + "3", "ddd");
|
||||
assertEquals("123", 1 + "2" + "3", "ndd");
|
||||
assertEquals("123", "1" + 2 + "3", "dnd");
|
||||
assertEquals("123", "1" + "2" + 3, "ddn");
|
||||
|
||||
assertEquals("123", "1" + 2 + 3, "dnn");
|
||||
assertEquals("123", 1 + "2" + 3, "ndn");
|
||||
assertEquals("33", 1 + 2 + "3", "nnd");
|
||||
|
||||
var x = "1";
|
||||
assertEquals("12", x + 2, "vn");
|
||||
assertEquals("12", x + "2", "vd");
|
||||
assertEquals("21", 2 + x, "nv");
|
||||
assertEquals("21", "2" + x, "dv");
|
||||
|
||||
var y = "2";
|
||||
assertEquals("12", x + y, "vdvd");
|
||||
|
||||
x = 1;
|
||||
assertEquals("12", x + y, "vnvd");
|
||||
|
||||
y = 2;
|
||||
assertEquals(3, x + y, "vnvn");
|
||||
|
||||
x = "1";
|
||||
assertEquals("12", x + y, "vdvn");
|
||||
|
||||
y = "2";
|
||||
assertEquals("12", x + y, "vdvd2");
|
||||
|
||||
(function(x, y) {
|
||||
var z = "3";
|
||||
var w = "4";
|
||||
|
||||
assertEquals("11", x + x, "xx");
|
||||
assertEquals("12", x + y, "xy");
|
||||
assertEquals("13", x + z, "xz");
|
||||
assertEquals("14", x + w, "xw");
|
||||
|
||||
assertEquals("21", y + x, "yx");
|
||||
assertEquals("22", y + y, "yy");
|
||||
assertEquals("23", y + z, "yz");
|
||||
assertEquals("24", y + w, "yw");
|
||||
|
||||
assertEquals("31", z + x, "zx");
|
||||
assertEquals("32", z + y, "zy");
|
||||
assertEquals("33", z + z, "zz");
|
||||
assertEquals("34", z + w, "zw");
|
||||
|
||||
assertEquals("41", w + x, "wx");
|
||||
assertEquals("42", w + y, "wy");
|
||||
assertEquals("43", w + z, "wz");
|
||||
assertEquals("44", w + w, "ww");
|
||||
|
||||
(function(){x = 1; z = 3;})();
|
||||
|
||||
assertEquals(2, x + x, "x'x");
|
||||
assertEquals("12", x + y, "x'y");
|
||||
assertEquals(4, x + z, "x'z'");
|
||||
assertEquals("14", x + w, "x'w");
|
||||
|
||||
assertEquals("21", y + x, "yx'");
|
||||
assertEquals("22", y + y, "yy");
|
||||
assertEquals("23", y + z, "yz'");
|
||||
assertEquals("24", y + w, "yw");
|
||||
|
||||
assertEquals(4, z + x, "z'x'");
|
||||
assertEquals("32", z + y, "z'y");
|
||||
assertEquals(6, z + z, "z'z'");
|
||||
assertEquals("34", z + w, "z'w");
|
||||
|
||||
assertEquals("41", w + x, "wx'");
|
||||
assertEquals("42", w + y, "wy");
|
||||
assertEquals("43", w + z, "wz'");
|
||||
assertEquals("44", w + w, "ww");
|
||||
})("1", "2");
|
||||
|
||||
assertEquals("142", "1" + new Number(42), "sN");
|
||||
assertEquals("421", new Number(42) + "1", "Ns");
|
||||
assertEquals(84, new Number(42) + new Number(42), "NN");
|
||||
|
||||
assertEquals("142", "1" + new String("42"), "sS");
|
||||
assertEquals("421", new String("42") + "1", "Ss");
|
||||
assertEquals("142", "1" + new String("42"), "sS");
|
||||
assertEquals("4242", new String("42") + new String("42"), "SS");
|
||||
|
||||
assertEquals("1true", "1" + true, "sb");
|
||||
assertEquals("true1", true + "1", "bs");
|
||||
assertEquals(2, true + true, "bs");
|
||||
|
||||
assertEquals("1true", "1" + new Boolean(true), "sB");
|
||||
assertEquals("true1", new Boolean(true) + "1", "Bs");
|
||||
assertEquals(2, new Boolean(true) + new Boolean(true), "Bs");
|
||||
|
||||
assertEquals("1undefined", "1" + void 0, "sv");
|
||||
assertEquals("undefined1", (void 0) + "1", "vs");
|
||||
assertTrue(isNaN(void 0 + void 0), "vv");
|
||||
|
||||
assertEquals("1null", "1" + null, "su");
|
||||
assertEquals("null1", null + "1", "us");
|
||||
assertEquals(0, null + null, "uu");
|
||||
|
||||
(function (i) {
|
||||
// Check that incoming frames are merged correctly.
|
||||
var x;
|
||||
var y;
|
||||
var z;
|
||||
var w;
|
||||
switch (i) {
|
||||
case 1: x = 42; y = "stry"; z = "strz"; w = 42; break;
|
||||
default: x = "strx", y = 42; z = "strz"; w = 42; break;
|
||||
}
|
||||
var resxx = x + x;
|
||||
var resxy = x + y;
|
||||
var resxz = x + z;
|
||||
var resxw = x + w;
|
||||
var resyx = y + x;
|
||||
var resyy = y + y;
|
||||
var resyz = y + z;
|
||||
var resyw = y + w;
|
||||
var reszx = z + x;
|
||||
var reszy = z + y;
|
||||
var reszz = z + z;
|
||||
var reszw = z + w;
|
||||
var reswx = w + x;
|
||||
var reswy = w + y;
|
||||
var reswz = w + z;
|
||||
var resww = w + w;
|
||||
assertEquals(84, resxx, "swxx");
|
||||
assertEquals("42stry", resxy, "swxy");
|
||||
assertEquals("42strz", resxz, "swxz");
|
||||
assertEquals(84, resxw, "swxw");
|
||||
assertEquals("stry42", resyx, "swyx");
|
||||
assertEquals("strystry", resyy, "swyy");
|
||||
assertEquals("strystrz", resyz, "swyz");
|
||||
assertEquals("stry42", resyw, "swyw");
|
||||
assertEquals("strz42", reszx, "swzx");
|
||||
assertEquals("strzstry", reszy, "swzy");
|
||||
assertEquals("strzstrz", reszz, "swzz");
|
||||
assertEquals("strz42", reszw, "swzw");
|
||||
assertEquals(84, reswx, "swwx");
|
||||
assertEquals("42stry", reswy, "swwy");
|
||||
assertEquals("42strz", reswz, "swwz");
|
||||
assertEquals(84, resww, "swww");
|
||||
})(1);
|
||||
|
||||
// Generate ascii and non ascii strings from length 0 to 20.
|
||||
var ascii = 'aaaaaaaaaaaaaaaaaaaa';
|
||||
var non_ascii = '\u1234\u1234\u1234\u1234\u1234\u1234\u1234\u1234\u1234\u1234\u1234\u1234\u1234\u1234\u1234\u1234\u1234\u1234\u1234\u1234';
|
||||
assertEquals(20, ascii.length);
|
||||
assertEquals(20, non_ascii.length);
|
||||
var a = Array(21);
|
||||
var b = Array(21);
|
||||
for (var i = 0; i <= 20; i++) {
|
||||
a[i] = ascii.substring(0, i);
|
||||
b[i] = non_ascii.substring(0, i);
|
||||
}
|
||||
|
||||
// Add ascii and non-ascii strings generating strings with length from 0 to 20.
|
||||
for (var i = 0; i <= 20; i++) {
|
||||
for (var j = 0; j < i; j++) {
|
||||
assertEquals(a[i], a[j] + a[i - j])
|
||||
assertEquals(b[i], b[j] + b[i - j])
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user