[TurboFan] Add support for generic lowering of StringConcat bytecode.
Adds support for lowering of ToPrimitiveToString and StringConcat bytecodes to the corresponding builtins. As part of this, moves the interpreter implementation of these operations into the appropriate builtin generators and add builtin support for them. Also adds TailCallRuntimeN operator to code-assembler which enables tail calling a runtime function when the arguments have already been pushed onto the stack. BUG=v8:6243 Change-Id: Id5c851bc42e4ff490d9a23a8990ae331c7eac73e Reviewed-on: https://chromium-review.googlesource.com/515362 Commit-Queue: Ross McIlroy <rmcilroy@chromium.org> Reviewed-by: Jaroslav Sevcik <jarin@chromium.org> Reviewed-by: Jakob Gruber <jgruber@chromium.org> Cr-Commit-Position: refs/heads/master@{#45756}
This commit is contained in:
parent
074b0464e2
commit
fdfb8c9efb
1
BUILD.gn
1
BUILD.gn
@ -939,6 +939,7 @@ v8_source_set("v8_builtins_generators") {
|
||||
"src/builtins/builtins-constructor-gen.h",
|
||||
"src/builtins/builtins-constructor.h",
|
||||
"src/builtins/builtins-conversion-gen.cc",
|
||||
"src/builtins/builtins-conversion-gen.h",
|
||||
"src/builtins/builtins-date-gen.cc",
|
||||
"src/builtins/builtins-debug-gen.cc",
|
||||
"src/builtins/builtins-forin-gen.cc",
|
||||
|
@ -49,6 +49,8 @@ const Register StoreTransitionDescriptor::MapRegister() { return r5; }
|
||||
const Register StringCompareDescriptor::LeftRegister() { return r1; }
|
||||
const Register StringCompareDescriptor::RightRegister() { return r0; }
|
||||
|
||||
const Register StringConcatDescriptor::ArgumentsCountRegister() { return r0; }
|
||||
|
||||
const Register ApiGetterDescriptor::HolderRegister() { return r0; }
|
||||
const Register ApiGetterDescriptor::CallbackRegister() { return r3; }
|
||||
|
||||
|
@ -49,6 +49,8 @@ const Register StoreTransitionDescriptor::MapRegister() { return x5; }
|
||||
const Register StringCompareDescriptor::LeftRegister() { return x1; }
|
||||
const Register StringCompareDescriptor::RightRegister() { return x0; }
|
||||
|
||||
const Register StringConcatDescriptor::ArgumentsCountRegister() { return x0; }
|
||||
|
||||
const Register ApiGetterDescriptor::HolderRegister() { return x0; }
|
||||
const Register ApiGetterDescriptor::CallbackRegister() { return x3; }
|
||||
|
||||
|
@ -2,28 +2,16 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/builtins/builtins-conversion-gen.h"
|
||||
|
||||
#include "src/builtins/builtins-utils-gen.h"
|
||||
#include "src/builtins/builtins.h"
|
||||
#include "src/code-factory.h"
|
||||
#include "src/code-stub-assembler.h"
|
||||
#include "src/objects-inl.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
class ConversionBuiltinsAssembler : public CodeStubAssembler {
|
||||
public:
|
||||
explicit ConversionBuiltinsAssembler(compiler::CodeAssemblerState* state)
|
||||
: CodeStubAssembler(state) {}
|
||||
|
||||
protected:
|
||||
void Generate_NonPrimitiveToPrimitive(Node* context, Node* input,
|
||||
ToPrimitiveHint hint);
|
||||
|
||||
void Generate_OrdinaryToPrimitive(Node* context, Node* input,
|
||||
OrdinaryToPrimitiveHint hint);
|
||||
};
|
||||
|
||||
// ES6 section 7.1.1 ToPrimitive ( input [ , PreferredType ] )
|
||||
void ConversionBuiltinsAssembler::Generate_NonPrimitiveToPrimitive(
|
||||
Node* context, Node* input, ToPrimitiveHint hint) {
|
||||
@ -136,6 +124,53 @@ TF_BUILTIN(ToString, CodeStubAssembler) {
|
||||
Return(ToString(context, input));
|
||||
}
|
||||
|
||||
// ES6 section 7.1.1 ToPrimitive( argument, "default" ) followed by
|
||||
// ES6 section 7.1.12 ToString ( argument )
|
||||
compiler::Node* ConversionBuiltinsAssembler::ToPrimitiveToString(
|
||||
Node* context, Node* input, Variable* feedback) {
|
||||
Label is_string(this), to_primitive(this, Label::kDeferred),
|
||||
to_string(this, Label::kDeferred), done(this);
|
||||
VARIABLE(result, MachineRepresentation::kTagged, input);
|
||||
|
||||
GotoIf(TaggedIsSmi(input), &to_string);
|
||||
GotoIf(IsString(input), &is_string);
|
||||
BranchIfJSReceiver(input, &to_primitive, &to_string);
|
||||
|
||||
BIND(&to_primitive);
|
||||
{
|
||||
Callable callable = CodeFactory::NonPrimitiveToPrimitive(isolate());
|
||||
result.Bind(CallStub(callable, context, input));
|
||||
Goto(&to_string);
|
||||
}
|
||||
|
||||
BIND(&to_string);
|
||||
{
|
||||
if (feedback) {
|
||||
feedback->Bind(SmiConstant(ToPrimitiveToStringFeedback::kAny));
|
||||
}
|
||||
result.Bind(CallBuiltin(Builtins::kToString, context, result.value()));
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
BIND(&is_string);
|
||||
{
|
||||
if (feedback) {
|
||||
feedback->Bind(SmiConstant(ToPrimitiveToStringFeedback::kString));
|
||||
}
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
BIND(&done);
|
||||
return result.value();
|
||||
}
|
||||
|
||||
TF_BUILTIN(ToPrimitiveToString, ConversionBuiltinsAssembler) {
|
||||
Node* context = Parameter(Descriptor::kContext);
|
||||
Node* input = Parameter(Descriptor::kArgument);
|
||||
|
||||
Return(ToPrimitiveToString(context, input));
|
||||
}
|
||||
|
||||
// 7.1.1.1 OrdinaryToPrimitive ( O, hint )
|
||||
void ConversionBuiltinsAssembler::Generate_OrdinaryToPrimitive(
|
||||
Node* context, Node* input, OrdinaryToPrimitiveHint hint) {
|
||||
|
32
src/builtins/builtins-conversion-gen.h
Normal file
32
src/builtins/builtins-conversion-gen.h
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2017 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef V8_BUILTINS_BUILTINS_CONVERSION_GEN_H_
|
||||
#define V8_BUILTINS_BUILTINS_CONVERSION_GEN_H_
|
||||
|
||||
#include "src/code-stub-assembler.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
class ConversionBuiltinsAssembler : public CodeStubAssembler {
|
||||
public:
|
||||
explicit ConversionBuiltinsAssembler(compiler::CodeAssemblerState* state)
|
||||
: CodeStubAssembler(state) {}
|
||||
|
||||
Node* ToPrimitiveToString(Node* context, Node* input,
|
||||
Variable* feedback = nullptr);
|
||||
|
||||
protected:
|
||||
void Generate_NonPrimitiveToPrimitive(Node* context, Node* input,
|
||||
ToPrimitiveHint hint);
|
||||
|
||||
void Generate_OrdinaryToPrimitive(Node* context, Node* input,
|
||||
OrdinaryToPrimitiveHint hint);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_BUILTINS_BUILTINS_CONVERSION_GEN_H_
|
@ -126,6 +126,7 @@ namespace internal {
|
||||
TFS(StringIndexOf, kReceiver, kSearchString, kPosition) \
|
||||
TFC(StringLessThan, Compare, 1) \
|
||||
TFC(StringLessThanOrEqual, Compare, 1) \
|
||||
TFC(StringConcat, StringConcat, 1) \
|
||||
\
|
||||
/* Interpreter */ \
|
||||
ASM(InterpreterEntryTrampoline) \
|
||||
@ -192,6 +193,7 @@ namespace internal {
|
||||
TFC(NonNumberToNumber, TypeConversion, 1) \
|
||||
TFC(ToNumber, TypeConversion, 1) \
|
||||
TFC(ToString, TypeConversion, 1) \
|
||||
TFC(ToPrimitiveToString, TypeConversion, 1) \
|
||||
TFC(ToInteger, TypeConversion, 1) \
|
||||
TFC(ToLength, TypeConversion, 1) \
|
||||
TFC(ClassOf, Typeof, 1) \
|
||||
|
@ -1821,5 +1821,258 @@ TF_BUILTIN(StringIteratorPrototypeNext, StringBuiltinsAssembler) {
|
||||
}
|
||||
}
|
||||
|
||||
Node* StringBuiltinsAssembler::ConcatenateSequentialStrings(
|
||||
Node* context, Node* first_arg_ptr, Node* last_arg_ptr, Node* total_length,
|
||||
String::Encoding encoding) {
|
||||
Node* result;
|
||||
if (encoding == String::ONE_BYTE_ENCODING) {
|
||||
result = AllocateSeqOneByteString(context, total_length, SMI_PARAMETERS);
|
||||
} else {
|
||||
DCHECK_EQ(String::TWO_BYTE_ENCODING, encoding);
|
||||
result = AllocateSeqTwoByteString(context, total_length, SMI_PARAMETERS);
|
||||
}
|
||||
|
||||
VARIABLE(current_arg, MachineType::PointerRepresentation(), first_arg_ptr);
|
||||
VARIABLE(str_index, MachineRepresentation::kTaggedSigned,
|
||||
SmiConstant(Smi::kZero));
|
||||
|
||||
Label loop(this, {¤t_arg, &str_index}), done(this);
|
||||
|
||||
Goto(&loop);
|
||||
BIND(&loop);
|
||||
{
|
||||
VARIABLE(current_string, MachineRepresentation::kTagged,
|
||||
Load(MachineType::AnyTagged(), current_arg.value()));
|
||||
|
||||
Label deref_indirect(this, Label::kDeferred),
|
||||
is_sequential(this, ¤t_string);
|
||||
|
||||
// Check if we need to dereference an indirect string.
|
||||
Node* instance_type = LoadInstanceType(current_string.value());
|
||||
Branch(IsSequentialStringInstanceType(instance_type), &is_sequential,
|
||||
&deref_indirect);
|
||||
|
||||
BIND(&is_sequential);
|
||||
{
|
||||
Node* current_length = LoadStringLength(current_string.value());
|
||||
CopyStringCharacters(current_string.value(), result,
|
||||
SmiConstant(Smi::kZero), str_index.value(),
|
||||
current_length, encoding, encoding, SMI_PARAMETERS);
|
||||
str_index.Bind(SmiAdd(str_index.value(), current_length));
|
||||
current_arg.Bind(
|
||||
IntPtrSub(current_arg.value(), IntPtrConstant(kPointerSize)));
|
||||
Branch(IntPtrGreaterThanOrEqual(current_arg.value(), last_arg_ptr), &loop,
|
||||
&done);
|
||||
}
|
||||
|
||||
BIND(&deref_indirect);
|
||||
{
|
||||
DerefIndirectString(¤t_string, instance_type);
|
||||
Goto(&is_sequential);
|
||||
}
|
||||
}
|
||||
BIND(&done);
|
||||
CSA_ASSERT(this, SmiEqual(str_index.value(), total_length));
|
||||
return result;
|
||||
}
|
||||
|
||||
Node* StringBuiltinsAssembler::ConcatenateStrings(Node* context,
|
||||
Node* first_arg_ptr,
|
||||
Node* arg_count,
|
||||
Label* bailout_to_runtime) {
|
||||
Label do_flat_string(this), do_cons_string(this), done(this);
|
||||
// There must be at least two strings being concatenated.
|
||||
CSA_ASSERT(this, Uint32GreaterThanOrEqual(arg_count, Int32Constant(2)));
|
||||
// Arguments grow up on the stack, so subtract arg_count - 1 from first_arg to
|
||||
// get the last argument to be concatenated.
|
||||
Node* last_arg_ptr = IntPtrSub(
|
||||
first_arg_ptr, TimesPointerSize(IntPtrSub(ChangeUint32ToWord(arg_count),
|
||||
IntPtrConstant(1))));
|
||||
|
||||
VARIABLE(current_arg, MachineType::PointerRepresentation(), first_arg_ptr);
|
||||
VARIABLE(current_string, MachineRepresentation::kTagged,
|
||||
Load(MachineType::AnyTagged(), current_arg.value()));
|
||||
VARIABLE(total_length, MachineRepresentation::kTaggedSigned,
|
||||
SmiConstant(Smi::kZero));
|
||||
VARIABLE(result, MachineRepresentation::kTagged);
|
||||
|
||||
Node* string_encoding = Word32And(LoadInstanceType(current_string.value()),
|
||||
Int32Constant(kStringEncodingMask));
|
||||
|
||||
Label flat_length_loop(this, {¤t_arg, ¤t_string, &total_length}),
|
||||
done_flat_length_loop(this);
|
||||
Goto(&flat_length_loop);
|
||||
BIND(&flat_length_loop);
|
||||
{
|
||||
Comment("Loop to find length and type of initial flat-string");
|
||||
Label is_sequential_or_can_deref(this);
|
||||
|
||||
// Increment total_length by the current string's length.
|
||||
Node* string_length = LoadStringLength(current_string.value());
|
||||
CSA_ASSERT(this, TaggedIsSmi(string_length));
|
||||
// No need to check for Smi overflow since String::kMaxLength is 2^28 - 16.
|
||||
total_length.Bind(SmiAdd(total_length.value(), string_length));
|
||||
|
||||
// If we are above the min cons string length, bailout.
|
||||
GotoIf(SmiAboveOrEqual(total_length.value(),
|
||||
SmiConstant(ConsString::kMinLength)),
|
||||
&done_flat_length_loop);
|
||||
|
||||
// Check that all the strings have the same encoding type. If we got here
|
||||
// we are still under ConsString::kMinLength so need to bailout to the
|
||||
// runtime if the strings have different encodings.
|
||||
Node* instance_type = LoadInstanceType(current_string.value());
|
||||
GotoIf(Word32NotEqual(
|
||||
string_encoding,
|
||||
Word32And(instance_type, Int32Constant(kStringEncodingMask))),
|
||||
bailout_to_runtime);
|
||||
|
||||
// Check if the new string is sequential or can be dereferenced as a
|
||||
// sequential string. If it can't and we've reached here, we are still under
|
||||
// ConsString::kMinLength so need to bailout to the runtime.
|
||||
GotoIf(IsSequentialStringInstanceType(instance_type),
|
||||
&is_sequential_or_can_deref);
|
||||
BranchIfCanDerefIndirectString(current_string.value(), instance_type,
|
||||
&is_sequential_or_can_deref,
|
||||
bailout_to_runtime);
|
||||
BIND(&is_sequential_or_can_deref);
|
||||
|
||||
current_arg.Bind(
|
||||
IntPtrSub(current_arg.value(), IntPtrConstant(kPointerSize)));
|
||||
GotoIf(IntPtrLessThan(current_arg.value(), last_arg_ptr),
|
||||
&done_flat_length_loop);
|
||||
current_string.Bind(Load(MachineType::AnyTagged(), current_arg.value()));
|
||||
Goto(&flat_length_loop);
|
||||
}
|
||||
BIND(&done_flat_length_loop);
|
||||
|
||||
// If new length is greater than String::kMaxLength, goto runtime to throw.
|
||||
GotoIf(SmiAboveOrEqual(total_length.value(), SmiConstant(String::kMaxLength)),
|
||||
bailout_to_runtime);
|
||||
|
||||
// If new length is less than ConsString::kMinLength, concatenate all operands
|
||||
// as a flat string.
|
||||
GotoIf(SmiLessThan(total_length.value(), SmiConstant(ConsString::kMinLength)),
|
||||
&do_flat_string);
|
||||
|
||||
// If the new length is is greater than ConsString::kMinLength, create a flat
|
||||
// string for first_arg to current_arg if there is at least two strings
|
||||
// between.
|
||||
{
|
||||
Comment("New length is greater than ConsString::kMinLength");
|
||||
|
||||
// Subtract length of the last string that pushed us over the edge.
|
||||
Node* string_length = LoadStringLength(current_string.value());
|
||||
total_length.Bind(SmiSub(total_length.value(), string_length));
|
||||
|
||||
// If we have 2 or more operands under ConsString::kMinLength, concatenate
|
||||
// them as a flat string before concatenating the rest as a cons string. We
|
||||
// concatenate the initial string as a flat string even though we will end
|
||||
// up with a cons string since the time and memory overheads of that initial
|
||||
// flat string will be less than they would be for concatenating the whole
|
||||
// string as cons strings.
|
||||
GotoIf(
|
||||
IntPtrGreaterThanOrEqual(IntPtrSub(first_arg_ptr, current_arg.value()),
|
||||
IntPtrConstant(2 * kPointerSize)),
|
||||
&do_flat_string);
|
||||
|
||||
// Otherwise the whole concatenation should be cons strings.
|
||||
result.Bind(Load(MachineType::AnyTagged(), first_arg_ptr));
|
||||
total_length.Bind(LoadStringLength(result.value()));
|
||||
current_arg.Bind(IntPtrSub(first_arg_ptr, IntPtrConstant(kPointerSize)));
|
||||
Goto(&do_cons_string);
|
||||
}
|
||||
|
||||
BIND(&do_flat_string);
|
||||
{
|
||||
Comment("Flat string concatenation");
|
||||
Node* last_flat_arg_ptr =
|
||||
IntPtrAdd(current_arg.value(), IntPtrConstant(kPointerSize));
|
||||
Label two_byte(this);
|
||||
GotoIf(Word32Equal(string_encoding, Int32Constant(kTwoByteStringTag)),
|
||||
&two_byte);
|
||||
|
||||
{
|
||||
Comment("One-byte sequential string case");
|
||||
result.Bind(ConcatenateSequentialStrings(
|
||||
context, first_arg_ptr, last_flat_arg_ptr, total_length.value(),
|
||||
String::ONE_BYTE_ENCODING));
|
||||
// If there is still more arguments to concatenate, jump to the cons
|
||||
// string case, otherwise we are done.
|
||||
Branch(IntPtrLessThan(current_arg.value(), last_arg_ptr), &done,
|
||||
&do_cons_string);
|
||||
}
|
||||
|
||||
BIND(&two_byte);
|
||||
{
|
||||
Comment("Two-byte sequential string case");
|
||||
result.Bind(ConcatenateSequentialStrings(
|
||||
context, first_arg_ptr, last_flat_arg_ptr, total_length.value(),
|
||||
String::TWO_BYTE_ENCODING));
|
||||
// If there is still more arguments to concatenate, jump to the cons
|
||||
// string case, otherwise we are done.
|
||||
Branch(IntPtrLessThan(current_arg.value(), last_arg_ptr), &done,
|
||||
&do_cons_string);
|
||||
}
|
||||
}
|
||||
|
||||
BIND(&do_cons_string);
|
||||
{
|
||||
Comment("Create cons string");
|
||||
Label loop(this, {¤t_arg, &total_length, &result}), done_cons(this);
|
||||
|
||||
Goto(&loop);
|
||||
BIND(&loop);
|
||||
{
|
||||
Node* current_string =
|
||||
Load(MachineType::AnyTagged(), current_arg.value());
|
||||
Node* string_length = LoadStringLength(current_string);
|
||||
|
||||
// Skip concatenating empty string.
|
||||
GotoIf(SmiEqual(string_length, SmiConstant(Smi::kZero)), &done_cons);
|
||||
|
||||
total_length.Bind(SmiAdd(total_length.value(), string_length));
|
||||
|
||||
// If new length is greater than String::kMaxLength, goto runtime to
|
||||
// throw. Note: we also need to invalidate the string length protector, so
|
||||
// can't just throw here directly.
|
||||
GotoIf(SmiAboveOrEqual(total_length.value(),
|
||||
SmiConstant(String::kMaxLength)),
|
||||
bailout_to_runtime);
|
||||
|
||||
result.Bind(NewConsString(context, total_length.value(), result.value(),
|
||||
current_string, CodeStubAssembler::kNone));
|
||||
Goto(&done_cons);
|
||||
|
||||
BIND(&done_cons);
|
||||
current_arg.Bind(
|
||||
IntPtrSub(current_arg.value(), IntPtrConstant(kPointerSize)));
|
||||
Branch(IntPtrLessThan(current_arg.value(), last_arg_ptr), &done, &loop);
|
||||
}
|
||||
}
|
||||
|
||||
BIND(&done);
|
||||
IncrementCounter(isolate()->counters()->string_add_native(), 1);
|
||||
return result.value();
|
||||
}
|
||||
|
||||
TF_BUILTIN(StringConcat, StringBuiltinsAssembler) {
|
||||
Node* argc = Parameter(Descriptor::kArgumentsCount);
|
||||
Node* context = Parameter(Descriptor::kContext);
|
||||
|
||||
CodeStubArguments args(this, ChangeInt32ToIntPtr(argc),
|
||||
CodeStubArguments::ReceiverMode::kNoReceiver);
|
||||
Node* first_arg_ptr =
|
||||
args.AtIndexPtr(IntPtrConstant(0), ParameterMode::INTPTR_PARAMETERS);
|
||||
|
||||
Label call_runtime(this, Label::kDeferred);
|
||||
Node* result =
|
||||
ConcatenateStrings(context, first_arg_ptr, argc, &call_runtime);
|
||||
args.PopAndReturn(result);
|
||||
|
||||
BIND(&call_runtime);
|
||||
TailCallRuntimeN(Runtime::kStringConcat, context, argc);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -24,6 +24,10 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
|
||||
Label* if_equal, Label* if_not_equal,
|
||||
Label* if_notbothdirectonebyte);
|
||||
|
||||
// String concatenation.
|
||||
Node* ConcatenateStrings(Node* context, Node* first_arg_ptr, Node* arg_count,
|
||||
Label* bailout_to_runtime);
|
||||
|
||||
protected:
|
||||
Node* DirectStringData(Node* string, Node* string_instance_type);
|
||||
|
||||
@ -54,6 +58,10 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
|
||||
Node* LoadSurrogatePairAt(Node* string, Node* length, Node* index,
|
||||
UnicodeEncoding encoding);
|
||||
|
||||
Node* ConcatenateSequentialStrings(Node* context, Node* first_arg_ptr,
|
||||
Node* arg_count, Node* total_length,
|
||||
String::Encoding encoding);
|
||||
|
||||
void StringIndexOf(Node* const subject_string,
|
||||
Node* const subject_instance_type,
|
||||
Node* const search_string,
|
||||
|
@ -9175,21 +9175,23 @@ Node* CodeStubAssembler::IsDetachedBuffer(Node* buffer) {
|
||||
return IsSetWord32<JSArrayBuffer::WasNeutered>(buffer_bit_field);
|
||||
}
|
||||
|
||||
CodeStubArguments::CodeStubArguments(CodeStubAssembler* assembler, Node* argc,
|
||||
Node* fp,
|
||||
CodeStubAssembler::ParameterMode mode)
|
||||
CodeStubArguments::CodeStubArguments(
|
||||
CodeStubAssembler* assembler, Node* argc, Node* fp,
|
||||
CodeStubAssembler::ParameterMode param_mode, ReceiverMode receiver_mode)
|
||||
: assembler_(assembler),
|
||||
argc_mode_(mode),
|
||||
argc_mode_(param_mode),
|
||||
receiver_mode_(receiver_mode),
|
||||
argc_(argc),
|
||||
arguments_(nullptr),
|
||||
fp_(fp != nullptr ? fp : assembler_->LoadFramePointer()) {
|
||||
Node* offset = assembler_->ElementOffsetFromIndex(
|
||||
argc_, FAST_ELEMENTS, mode,
|
||||
argc_, FAST_ELEMENTS, param_mode,
|
||||
(StandardFrameConstants::kFixedSlotCountAboveFp - 1) * kPointerSize);
|
||||
arguments_ = assembler_->IntPtrAdd(fp_, offset);
|
||||
}
|
||||
|
||||
Node* CodeStubArguments::GetReceiver() const {
|
||||
DCHECK_EQ(receiver_mode_, ReceiverMode::kHasReceiver);
|
||||
return assembler_->Load(MachineType::AnyTagged(), arguments_,
|
||||
assembler_->IntPtrConstant(kPointerSize));
|
||||
}
|
||||
@ -9267,8 +9269,14 @@ void CodeStubArguments::ForEach(
|
||||
}
|
||||
|
||||
void CodeStubArguments::PopAndReturn(Node* value) {
|
||||
assembler_->PopAndReturn(
|
||||
assembler_->IntPtrAdd(argc_, assembler_->IntPtrConstant(1)), value);
|
||||
Node* pop_count;
|
||||
if (receiver_mode_ == ReceiverMode::kHasReceiver) {
|
||||
pop_count = assembler_->IntPtrOrSmiAdd(
|
||||
argc_, assembler_->IntPtrOrSmiConstant(1, argc_mode_), argc_mode_);
|
||||
} else {
|
||||
pop_count = argc_;
|
||||
}
|
||||
assembler_->PopAndReturn(pop_count, value);
|
||||
}
|
||||
|
||||
Node* CodeStubAssembler::IsFastElementsKind(Node* elements_kind) {
|
||||
|
@ -1527,15 +1527,21 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
|
||||
class CodeStubArguments {
|
||||
public:
|
||||
typedef compiler::Node Node;
|
||||
enum ReceiverMode { kHasReceiver, kNoReceiver };
|
||||
|
||||
// |argc| is an intptr value which specifies the number of arguments passed
|
||||
// to the builtin excluding the receiver.
|
||||
CodeStubArguments(CodeStubAssembler* assembler, Node* argc)
|
||||
// to the builtin excluding the receiver. The arguments will include a
|
||||
// receiver iff |receiver_mode| is kHasReceiver.
|
||||
CodeStubArguments(CodeStubAssembler* assembler, Node* argc,
|
||||
ReceiverMode receiver_mode = ReceiverMode::kHasReceiver)
|
||||
: CodeStubArguments(assembler, argc, nullptr,
|
||||
CodeStubAssembler::INTPTR_PARAMETERS) {}
|
||||
// |argc| is either a smi or intptr depending on |param_mode|
|
||||
CodeStubAssembler::INTPTR_PARAMETERS, receiver_mode) {
|
||||
}
|
||||
// |argc| is either a smi or intptr depending on |param_mode|. The arguments
|
||||
// include a receiver iff |receiver_mode| is kHasReceiver.
|
||||
CodeStubArguments(CodeStubAssembler* assembler, Node* argc, Node* fp,
|
||||
CodeStubAssembler::ParameterMode param_mode);
|
||||
CodeStubAssembler::ParameterMode param_mode,
|
||||
ReceiverMode receiver_mode = ReceiverMode::kHasReceiver);
|
||||
|
||||
Node* GetReceiver() const;
|
||||
|
||||
@ -1575,6 +1581,7 @@ class CodeStubArguments {
|
||||
|
||||
CodeStubAssembler* assembler_;
|
||||
CodeStubAssembler::ParameterMode argc_mode_;
|
||||
ReceiverMode receiver_mode_;
|
||||
Node* argc_;
|
||||
Node* arguments_;
|
||||
Node* fp_;
|
||||
|
@ -2174,11 +2174,28 @@ void BytecodeGraphBuilder::VisitToNumber() {
|
||||
}
|
||||
|
||||
void BytecodeGraphBuilder::VisitToPrimitiveToString() {
|
||||
UNREACHABLE(); // TODO(rmcilroy): Implement this.
|
||||
PrepareEagerCheckpoint();
|
||||
Node* object = environment()->LookupAccumulator();
|
||||
Node* node = NewNode(javascript()->ToPrimitiveToString(), object);
|
||||
environment()->BindRegister(bytecode_iterator().GetRegisterOperand(0), node,
|
||||
Environment::kAttachFrameState);
|
||||
}
|
||||
|
||||
void BytecodeGraphBuilder::VisitStringConcat() {
|
||||
UNREACHABLE(); // TODO(rmcilroy): Implement this.
|
||||
interpreter::Register first_reg = bytecode_iterator().GetRegisterOperand(0);
|
||||
int operand_count =
|
||||
static_cast<int>(bytecode_iterator().GetRegisterCountOperand(1));
|
||||
Node** operands =
|
||||
local_zone()->NewArray<Node*>(static_cast<size_t>(operand_count));
|
||||
int operand_base = first_reg.index();
|
||||
for (int i = 0; i < operand_count; ++i) {
|
||||
operands[i] =
|
||||
environment()->LookupRegister(interpreter::Register(operand_base + i));
|
||||
}
|
||||
|
||||
Node* node = MakeNode(javascript()->StringConcat(operand_count),
|
||||
operand_count, operands, false);
|
||||
environment()->BindAccumulator(node, Environment::kAttachFrameState);
|
||||
}
|
||||
|
||||
void BytecodeGraphBuilder::VisitJump() { BuildJump(); }
|
||||
|
@ -620,6 +620,22 @@ Node* CodeAssembler::TailCallRuntime(Runtime::FunctionId function,
|
||||
return raw_assembler()->TailCallN(desc, arraysize(nodes), nodes);
|
||||
}
|
||||
|
||||
Node* CodeAssembler::TailCallRuntimeN(Runtime::FunctionId function,
|
||||
Node* context, Node* argc) {
|
||||
CallDescriptor* desc = Linkage::GetRuntimeCallDescriptor(
|
||||
zone(), function, 0, Operator::kNoProperties,
|
||||
CallDescriptor::kSupportsTailCalls);
|
||||
int return_count = static_cast<int>(desc->ReturnCount());
|
||||
|
||||
Node* centry =
|
||||
HeapConstant(CodeFactory::RuntimeCEntry(isolate(), return_count));
|
||||
Node* ref = ExternalConstant(ExternalReference(function, isolate()));
|
||||
|
||||
Node* nodes[] = {centry, ref, argc, context};
|
||||
|
||||
return raw_assembler()->TailCallN(desc, arraysize(nodes), nodes);
|
||||
}
|
||||
|
||||
// Instantiate TailCallRuntime() for argument counts used by CSA-generated code
|
||||
#define INSTANTIATE(...) \
|
||||
template V8_EXPORT_PRIVATE Node* CodeAssembler::TailCallRuntime( \
|
||||
|
@ -351,6 +351,11 @@ class V8_EXPORT_PRIVATE CodeAssembler {
|
||||
Node* TailCallRuntime(Runtime::FunctionId function, Node* context,
|
||||
TArgs... args);
|
||||
|
||||
// Tail call into the runtime passing the same |argc| stack arguments that we
|
||||
// were called with.
|
||||
Node* TailCallRuntimeN(Runtime::FunctionId function, Node* context,
|
||||
Node* argc);
|
||||
|
||||
template <class... TArgs>
|
||||
Node* CallStub(Callable const& callable, Node* context, TArgs... args) {
|
||||
Node* target = HeapConstant(callable.code());
|
||||
|
@ -79,6 +79,7 @@ REPLACE_STUB_CALL(ToNumber)
|
||||
REPLACE_STUB_CALL(ToName)
|
||||
REPLACE_STUB_CALL(ToObject)
|
||||
REPLACE_STUB_CALL(ToString)
|
||||
REPLACE_STUB_CALL(ToPrimitiveToString)
|
||||
#undef REPLACE_STUB_CALL
|
||||
|
||||
void JSGenericLowering::ReplaceWithStubCall(Node* node, Callable callable,
|
||||
@ -153,6 +154,19 @@ void JSGenericLowering::LowerJSTypeOf(Node* node) {
|
||||
Operator::kEliminatable);
|
||||
}
|
||||
|
||||
void JSGenericLowering::LowerJSStringConcat(Node* node) {
|
||||
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
|
||||
int operand_count = StringConcatParameterOf(node->op()).operand_count();
|
||||
Callable callable = Builtins::CallableFor(isolate(), Builtins::kStringConcat);
|
||||
const CallInterfaceDescriptor& descriptor = callable.descriptor();
|
||||
CallDescriptor* desc = Linkage::GetStubCallDescriptor(
|
||||
isolate(), zone(), descriptor, operand_count, flags,
|
||||
node->op()->properties());
|
||||
Node* stub_code = jsgraph()->HeapConstant(callable.code());
|
||||
node->InsertInput(zone(), 0, stub_code);
|
||||
node->InsertInput(zone(), 1, jsgraph()->Int32Constant(operand_count));
|
||||
NodeProperties::ChangeOp(node, common()->Call(desc));
|
||||
}
|
||||
|
||||
void JSGenericLowering::LowerJSLoadProperty(Node* node) {
|
||||
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
|
||||
@ -171,7 +185,6 @@ void JSGenericLowering::LowerJSLoadProperty(Node* node) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void JSGenericLowering::LowerJSLoadNamed(Node* node) {
|
||||
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
|
||||
NamedAccess const& p = NamedAccessOf(node->op());
|
||||
|
@ -116,6 +116,29 @@ SpreadWithArityParameter const& SpreadWithArityParameterOf(Operator const* op) {
|
||||
return OpParameter<SpreadWithArityParameter>(op);
|
||||
}
|
||||
|
||||
bool operator==(StringConcatParameter const& lhs,
|
||||
StringConcatParameter const& rhs) {
|
||||
return lhs.operand_count() == rhs.operand_count();
|
||||
}
|
||||
|
||||
bool operator!=(StringConcatParameter const& lhs,
|
||||
StringConcatParameter const& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
size_t hash_value(StringConcatParameter const& p) {
|
||||
return base::hash_combine(p.operand_count());
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, StringConcatParameter const& p) {
|
||||
return os << p.operand_count();
|
||||
}
|
||||
|
||||
StringConcatParameter const& StringConcatParameterOf(Operator const* op) {
|
||||
DCHECK(op->opcode() == IrOpcode::kJSStringConcat);
|
||||
return OpParameter<StringConcatParameter>(op);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, CallParameters const& p) {
|
||||
os << p.arity() << ", " << p.frequency() << ", " << p.convert_mode() << ", "
|
||||
<< p.tail_call_mode();
|
||||
@ -590,6 +613,7 @@ CompareOperationHint CompareOperationHintOf(const Operator* op) {
|
||||
V(ToNumber, Operator::kNoProperties, 1, 1) \
|
||||
V(ToObject, Operator::kFoldable, 1, 1) \
|
||||
V(ToString, Operator::kNoProperties, 1, 1) \
|
||||
V(ToPrimitiveToString, Operator::kNoProperties, 1, 1) \
|
||||
V(Create, Operator::kNoProperties, 2, 1) \
|
||||
V(CreateIterResultObject, Operator::kEliminatable, 2, 1) \
|
||||
V(CreateKeyValueArray, Operator::kEliminatable, 2, 1) \
|
||||
@ -740,6 +764,15 @@ BINARY_OP_LIST(BINARY_OP)
|
||||
COMPARE_OP_LIST(COMPARE_OP)
|
||||
#undef COMPARE_OP
|
||||
|
||||
const Operator* JSOperatorBuilder::StringConcat(int operand_count) {
|
||||
StringConcatParameter parameters(operand_count);
|
||||
return new (zone()) Operator1<StringConcatParameter>( // --
|
||||
IrOpcode::kJSStringConcat, Operator::kNoProperties, // opcode
|
||||
"JSStringConcat", // name
|
||||
operand_count, 1, 1, 1, 1, 2, // counts
|
||||
parameters); // parameter
|
||||
}
|
||||
|
||||
const Operator* JSOperatorBuilder::StoreDataPropertyInLiteral(
|
||||
const VectorSlotPair& feedback) {
|
||||
FeedbackParameter parameters(feedback);
|
||||
@ -1011,7 +1044,6 @@ const Operator* JSOperatorBuilder::CreateArguments(CreateArgumentsType type) {
|
||||
type); // parameter
|
||||
}
|
||||
|
||||
|
||||
const Operator* JSOperatorBuilder::CreateArray(size_t arity,
|
||||
Handle<AllocationSite> site) {
|
||||
// constructor, new_target, arg1, ..., argN
|
||||
|
@ -619,6 +619,27 @@ std::ostream& operator<<(std::ostream&, CreateLiteralParameters const&);
|
||||
|
||||
const CreateLiteralParameters& CreateLiteralParametersOf(const Operator* op);
|
||||
|
||||
// Defines the number of operands passed to a JSStringConcat operator.
|
||||
class StringConcatParameter final {
|
||||
public:
|
||||
explicit StringConcatParameter(int operand_count)
|
||||
: operand_count_(operand_count) {}
|
||||
|
||||
int operand_count() const { return operand_count_; }
|
||||
|
||||
private:
|
||||
uint32_t const operand_count_;
|
||||
};
|
||||
|
||||
bool operator==(StringConcatParameter const&, StringConcatParameter const&);
|
||||
bool operator!=(StringConcatParameter const&, StringConcatParameter const&);
|
||||
|
||||
size_t hash_value(StringConcatParameter const&);
|
||||
|
||||
std::ostream& operator<<(std::ostream&, StringConcatParameter const&);
|
||||
|
||||
StringConcatParameter const& StringConcatParameterOf(Operator const*);
|
||||
|
||||
class GeneratorStoreParameters final {
|
||||
public:
|
||||
GeneratorStoreParameters(int register_count, SuspendFlags flags)
|
||||
@ -684,6 +705,7 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
|
||||
const Operator* ToNumber();
|
||||
const Operator* ToObject();
|
||||
const Operator* ToString();
|
||||
const Operator* ToPrimitiveToString();
|
||||
|
||||
const Operator* Create();
|
||||
const Operator* CreateArguments(CreateArgumentsType type);
|
||||
@ -766,6 +788,8 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
|
||||
const Operator* LoadMessage();
|
||||
const Operator* StoreMessage();
|
||||
|
||||
const Operator* StringConcat(int operand_count);
|
||||
|
||||
// Used to implement Ignition's SuspendGenerator bytecode.
|
||||
const Operator* GeneratorStore(int register_count,
|
||||
SuspendFlags suspend_flags);
|
||||
|
@ -1163,6 +1163,7 @@ Reduction JSTypedLowering::ReduceJSToStringInput(Node* input) {
|
||||
}
|
||||
|
||||
Reduction JSTypedLowering::ReduceJSToString(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSToString, node->opcode());
|
||||
// Try to reduce the input first.
|
||||
Node* const input = node->InputAt(0);
|
||||
Reduction reduction = ReduceJSToStringInput(input);
|
||||
@ -1173,6 +1174,23 @@ Reduction JSTypedLowering::ReduceJSToString(Node* node) {
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
Reduction JSTypedLowering::ReduceJSToPrimitiveToString(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSToPrimitiveToString, node->opcode());
|
||||
Node* input = NodeProperties::GetValueInput(node, 0);
|
||||
Type* input_type = NodeProperties::GetType(input);
|
||||
if (input_type->Is(Type::Primitive())) {
|
||||
// If node is already a primitive, then reduce to JSToString and try to
|
||||
// reduce that further.
|
||||
NodeProperties::ChangeOp(node, javascript()->ToString());
|
||||
Reduction reduction = ReduceJSToString(node);
|
||||
if (reduction.Changed()) {
|
||||
return reduction;
|
||||
}
|
||||
return Changed(node);
|
||||
}
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
Reduction JSTypedLowering::ReduceJSToObject(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSToObject, node->opcode());
|
||||
Node* receiver = NodeProperties::GetValueInput(node, 0);
|
||||
@ -2244,6 +2262,8 @@ Reduction JSTypedLowering::Reduce(Node* node) {
|
||||
return ReduceJSToNumber(node);
|
||||
case IrOpcode::kJSToString:
|
||||
return ReduceJSToString(node);
|
||||
case IrOpcode::kJSToPrimitiveToString:
|
||||
return ReduceJSToPrimitiveToString(node);
|
||||
case IrOpcode::kJSToObject:
|
||||
return ReduceJSToObject(node);
|
||||
case IrOpcode::kJSTypeOf:
|
||||
|
@ -67,6 +67,7 @@ class V8_EXPORT_PRIVATE JSTypedLowering final
|
||||
Reduction ReduceJSToNumber(Node* node);
|
||||
Reduction ReduceJSToStringInput(Node* input);
|
||||
Reduction ReduceJSToString(Node* node);
|
||||
Reduction ReduceJSToPrimitiveToString(Node* node);
|
||||
Reduction ReduceJSToObject(Node* node);
|
||||
Reduction ReduceJSConvertReceiver(Node* node);
|
||||
Reduction ReduceJSConstructForwardVarargs(Node* node);
|
||||
|
@ -116,7 +116,8 @@
|
||||
V(JSToName) \
|
||||
V(JSToNumber) \
|
||||
V(JSToObject) \
|
||||
V(JSToString)
|
||||
V(JSToString) \
|
||||
V(JSToPrimitiveToString)
|
||||
|
||||
#define JS_OTHER_UNOP_LIST(V) \
|
||||
V(JSClassOf) \
|
||||
@ -177,6 +178,7 @@
|
||||
V(JSGeneratorRestoreContinuation) \
|
||||
V(JSGeneratorRestoreRegister) \
|
||||
V(JSStackCheck) \
|
||||
V(JSStringConcat) \
|
||||
V(JSDebugger)
|
||||
|
||||
#define JS_OP_LIST(V) \
|
||||
|
@ -94,6 +94,7 @@ bool OperatorProperties::HasFrameStateInput(const Operator* op) {
|
||||
case IrOpcode::kJSToNumber:
|
||||
case IrOpcode::kJSToObject:
|
||||
case IrOpcode::kJSToString:
|
||||
case IrOpcode::kJSToPrimitiveToString:
|
||||
|
||||
// Call operations
|
||||
case IrOpcode::kJSConstructForwardVarargs:
|
||||
|
@ -2908,6 +2908,7 @@ class RepresentationSelector {
|
||||
case IrOpcode::kJSToName:
|
||||
case IrOpcode::kJSToObject:
|
||||
case IrOpcode::kJSToString:
|
||||
case IrOpcode::kJSToPrimitiveToString:
|
||||
VisitInputs(node);
|
||||
// Assume the output is tagged.
|
||||
return SetOutput(node, MachineRepresentation::kTagged);
|
||||
|
@ -270,6 +270,7 @@ class Typer::Visitor : public Reducer {
|
||||
static Type* ToNumber(Type*, Typer*);
|
||||
static Type* ToObject(Type*, Typer*);
|
||||
static Type* ToString(Type*, Typer*);
|
||||
static Type* ToPrimitiveToString(Type*, Typer*);
|
||||
#define DECLARE_METHOD(Name) \
|
||||
static Type* Name(Type* type, Typer* t) { \
|
||||
return t->operation_typer_.Name(type); \
|
||||
@ -504,6 +505,15 @@ Type* Typer::Visitor::ToString(Type* type, Typer* t) {
|
||||
return Type::String();
|
||||
}
|
||||
|
||||
// static
|
||||
Type* Typer::Visitor::ToPrimitiveToString(Type* type, Typer* t) {
|
||||
// ES6 section 7.1.1 ToPrimitive( argument, "default" ) followed by
|
||||
// ES6 section 7.1.12 ToString ( argument )
|
||||
type = ToPrimitive(type, t);
|
||||
if (type->Is(Type::String())) return type;
|
||||
return Type::String();
|
||||
}
|
||||
|
||||
// Type checks.
|
||||
|
||||
Type* Typer::Visitor::ObjectIsDetectableCallable(Type* type, Typer* t) {
|
||||
@ -1006,6 +1016,9 @@ Type* Typer::Visitor::JSShiftRightLogicalTyper(Type* lhs, Type* rhs, Typer* t) {
|
||||
return NumberShiftRightLogical(ToNumber(lhs, t), ToNumber(rhs, t), t);
|
||||
}
|
||||
|
||||
// JS string concatenation.
|
||||
|
||||
Type* Typer::Visitor::TypeJSStringConcat(Node* node) { return Type::String(); }
|
||||
|
||||
// JS arithmetic operators.
|
||||
|
||||
@ -1082,6 +1095,10 @@ Type* Typer::Visitor::TypeJSToString(Node* node) {
|
||||
return TypeUnaryOp(node, ToString);
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::TypeJSToPrimitiveToString(Node* node) {
|
||||
return TypeUnaryOp(node, ToPrimitiveToString);
|
||||
}
|
||||
|
||||
// JS object operators.
|
||||
|
||||
|
||||
|
@ -543,6 +543,16 @@ void Verifier::Visitor::Check(Node* node) {
|
||||
// Type is 32 bit integral.
|
||||
CheckTypeIs(node, Type::Integral32());
|
||||
break;
|
||||
|
||||
case IrOpcode::kJSStringConcat:
|
||||
// Type is string and all inputs are strings.
|
||||
CheckTypeIs(node, Type::String());
|
||||
for (int i = 0; i < StringConcatParameterOf(node->op()).operand_count();
|
||||
i++) {
|
||||
CheckValueInputIs(node, i, Type::String());
|
||||
}
|
||||
break;
|
||||
|
||||
case IrOpcode::kJSAdd:
|
||||
// Type is Number or String.
|
||||
CheckTypeIs(node, Type::NumberOrString());
|
||||
@ -575,6 +585,7 @@ void Verifier::Visitor::Check(Node* node) {
|
||||
CheckTypeIs(node, Type::Number());
|
||||
break;
|
||||
case IrOpcode::kJSToString:
|
||||
case IrOpcode::kJSToPrimitiveToString:
|
||||
// Type is String.
|
||||
CheckTypeIs(node, Type::String());
|
||||
break;
|
||||
|
@ -439,6 +439,7 @@ bool BytecodeHasNoSideEffect(interpreter::Bytecode bytecode) {
|
||||
case Bytecode::kToNumber:
|
||||
case Bytecode::kToName:
|
||||
// Misc.
|
||||
case Bytecode::kStringConcat:
|
||||
case Bytecode::kForInPrepare:
|
||||
case Bytecode::kForInContinue:
|
||||
case Bytecode::kForInNext:
|
||||
|
@ -47,6 +47,8 @@ const Register StoreTransitionDescriptor::MapRegister() { return edi; }
|
||||
const Register StringCompareDescriptor::LeftRegister() { return edx; }
|
||||
const Register StringCompareDescriptor::RightRegister() { return eax; }
|
||||
|
||||
const Register StringConcatDescriptor::ArgumentsCountRegister() { return eax; }
|
||||
|
||||
const Register ApiGetterDescriptor::HolderRegister() { return ecx; }
|
||||
const Register ApiGetterDescriptor::CallbackRegister() { return eax; }
|
||||
|
||||
|
@ -258,6 +258,20 @@ void StringCompareDescriptor::InitializePlatformSpecific(
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
void StringConcatDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {ArgumentsCountRegister()};
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
void StringConcatDescriptor::InitializePlatformIndependent(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
// kArgumentsCount
|
||||
MachineType machine_types[] = {MachineType::Int32()};
|
||||
data->InitializePlatformIndependent(arraysize(machine_types), 0,
|
||||
machine_types);
|
||||
}
|
||||
|
||||
void TypeConversionDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {ArgumentRegister()};
|
||||
|
@ -67,6 +67,7 @@ class PlatformInterfaceDescriptor;
|
||||
V(StringCharAt) \
|
||||
V(StringCharCodeAt) \
|
||||
V(StringCompare) \
|
||||
V(StringConcat) \
|
||||
V(SubString) \
|
||||
V(ForInPrepare) \
|
||||
V(GetProperty) \
|
||||
@ -689,7 +690,6 @@ class ArrayNArgumentsConstructorDescriptor : public CallInterfaceDescriptor {
|
||||
ArrayNArgumentsConstructorDescriptor, CallInterfaceDescriptor)
|
||||
};
|
||||
|
||||
|
||||
class CompareDescriptor : public CallInterfaceDescriptor {
|
||||
public:
|
||||
DEFINE_PARAMETERS(kLeft, kRight)
|
||||
@ -752,6 +752,15 @@ class StringCompareDescriptor : public CallInterfaceDescriptor {
|
||||
static const Register RightRegister();
|
||||
};
|
||||
|
||||
class StringConcatDescriptor : public CallInterfaceDescriptor {
|
||||
public:
|
||||
DEFINE_PARAMETERS(kArgumentsCount)
|
||||
DECLARE_DESCRIPTOR_WITH_CUSTOM_FUNCTION_TYPE(StringConcatDescriptor,
|
||||
CallInterfaceDescriptor)
|
||||
|
||||
static const Register ArgumentsCountRegister();
|
||||
};
|
||||
|
||||
class SubStringDescriptor : public CallInterfaceDescriptor {
|
||||
public:
|
||||
DEFINE_PARAMETERS(kString, kFrom, kTo)
|
||||
|
@ -9,7 +9,9 @@
|
||||
|
||||
#include "src/builtins/builtins-arguments-gen.h"
|
||||
#include "src/builtins/builtins-constructor-gen.h"
|
||||
#include "src/builtins/builtins-conversion-gen.h"
|
||||
#include "src/builtins/builtins-forin-gen.h"
|
||||
#include "src/builtins/builtins-string-gen.h"
|
||||
#include "src/code-events.h"
|
||||
#include "src/code-factory.h"
|
||||
#include "src/factory.h"
|
||||
@ -1474,290 +1476,38 @@ IGNITION_HANDLER(ToObject, InterpreterAssembler) {
|
||||
// Convert the object referenced by the accumulator to a primitive, and then
|
||||
// convert the operand to a string, in preparation to be used by StringConcat.
|
||||
IGNITION_HANDLER(ToPrimitiveToString, InterpreterAssembler) {
|
||||
Label is_string(this), to_primitive(this, Label::kDeferred),
|
||||
to_string(this, Label::kDeferred), done(this);
|
||||
Node* input = GetAccumulator();
|
||||
VARIABLE(result, MachineRepresentation::kTagged, input);
|
||||
VARIABLE(feedback, MachineRepresentation::kTagged);
|
||||
ConversionBuiltinsAssembler conversions_assembler(state());
|
||||
Node* result = conversions_assembler.ToPrimitiveToString(
|
||||
GetContext(), GetAccumulator(), &feedback);
|
||||
|
||||
GotoIf(TaggedIsSmi(input), &to_string);
|
||||
GotoIf(IsString(input), &is_string);
|
||||
BranchIfJSReceiver(input, &to_primitive, &to_string);
|
||||
|
||||
BIND(&to_primitive);
|
||||
{
|
||||
Callable callable = CodeFactory::NonPrimitiveToPrimitive(isolate());
|
||||
result.Bind(CallStub(callable, GetContext(), input));
|
||||
Goto(&to_string);
|
||||
}
|
||||
|
||||
BIND(&to_string);
|
||||
{
|
||||
feedback.Bind(SmiConstant(ToPrimitiveToStringFeedback::kAny));
|
||||
result.Bind(CallBuiltin(Builtins::kToString, GetContext(), result.value()));
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
BIND(&is_string);
|
||||
{
|
||||
feedback.Bind(SmiConstant(ToPrimitiveToStringFeedback::kString));
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
BIND(&done);
|
||||
UpdateFeedback(feedback.value(), LoadFeedbackVector(), BytecodeOperandIdx(1));
|
||||
StoreRegister(result.value(), BytecodeOperandReg(0));
|
||||
StoreRegister(result, BytecodeOperandReg(0));
|
||||
Dispatch();
|
||||
}
|
||||
|
||||
class InterpreterStringConcatAssembler : public InterpreterAssembler {
|
||||
public:
|
||||
InterpreterStringConcatAssembler(CodeAssemblerState* state, Bytecode bytecode,
|
||||
OperandScale operand_scale)
|
||||
: InterpreterAssembler(state, bytecode, operand_scale) {}
|
||||
|
||||
void CopySequentialStrings(Node* result, Node* first_reg_index,
|
||||
Node* last_reg_index, Node* final_size,
|
||||
String::Encoding encoding) {
|
||||
VARIABLE(copy_reg_index, MachineType::PointerRepresentation(),
|
||||
first_reg_index);
|
||||
VARIABLE(copy_str_index, MachineRepresentation::kTaggedSigned,
|
||||
SmiConstant(Smi::kZero));
|
||||
|
||||
Label copy_loop(this, {©_reg_index, ©_str_index}),
|
||||
done_copy_loop(this);
|
||||
|
||||
Goto(©_loop);
|
||||
BIND(©_loop);
|
||||
{
|
||||
VARIABLE(next_string, MachineRepresentation::kTagged);
|
||||
Label deref_indirect(this, Label::kDeferred),
|
||||
is_sequential(this, &next_string);
|
||||
|
||||
next_string.Bind(LoadRegister(copy_reg_index.value()));
|
||||
|
||||
// Check if we need to dereference an indirect string.
|
||||
Node* instance_type = LoadInstanceType(next_string.value());
|
||||
Branch(IsSequentialStringInstanceType(instance_type), &is_sequential,
|
||||
&deref_indirect);
|
||||
|
||||
BIND(&is_sequential);
|
||||
{
|
||||
Node* next_length = LoadStringLength(next_string.value());
|
||||
CopyStringCharacters(next_string.value(), result,
|
||||
SmiConstant(Smi::kZero), copy_str_index.value(),
|
||||
next_length, encoding, encoding, SMI_PARAMETERS);
|
||||
copy_str_index.Bind(SmiAdd(copy_str_index.value(), next_length));
|
||||
|
||||
copy_reg_index.Bind(NextRegister(copy_reg_index.value()));
|
||||
Branch(IntPtrGreaterThan(copy_reg_index.value(), last_reg_index),
|
||||
©_loop, &done_copy_loop);
|
||||
}
|
||||
|
||||
BIND(&deref_indirect);
|
||||
{
|
||||
DerefIndirectString(&next_string, instance_type);
|
||||
Goto(&is_sequential);
|
||||
}
|
||||
}
|
||||
BIND(&done_copy_loop);
|
||||
CSA_ASSERT(this, SmiEqual(copy_str_index.value(), final_size));
|
||||
}
|
||||
};
|
||||
|
||||
// StringConcat <first_reg> <reg_count>
|
||||
//
|
||||
// Concatenates the string values in registers <first_reg> to
|
||||
// <first_reg> + <reg_count - 1> and saves the result in the accumulator.
|
||||
IGNITION_HANDLER(StringConcat, InterpreterStringConcatAssembler) {
|
||||
Label do_flat_string(this), do_cons_string(this), done_native(this),
|
||||
done(this), call_runtime(this, Label::kDeferred);
|
||||
Node* first_reg_index = BytecodeOperandReg(0);
|
||||
IGNITION_HANDLER(StringConcat, InterpreterAssembler) {
|
||||
Label call_runtime(this, Label::kDeferred), done(this);
|
||||
|
||||
Node* first_reg_ptr = RegisterLocation(BytecodeOperandReg(0));
|
||||
Node* reg_count = BytecodeOperandCount(1);
|
||||
Node* context = GetContext();
|
||||
|
||||
// There must be at least two strings being concatenated.
|
||||
CSA_ASSERT(this, Uint32GreaterThanOrEqual(reg_count, Int32Constant(2)));
|
||||
// Register indexes are negative, so subtract reg_count-1 from first_index to
|
||||
// get the last register to be concatenated.
|
||||
Node* last_reg_index =
|
||||
IntPtrSub(first_reg_index,
|
||||
IntPtrAdd(ChangeUint32ToWord(reg_count), IntPtrConstant(-1)));
|
||||
|
||||
VARIABLE(reg_index, MachineType::PointerRepresentation(), first_reg_index);
|
||||
VARIABLE(current_string, MachineRepresentation::kTagged,
|
||||
LoadRegister(reg_index.value()));
|
||||
VARIABLE(total_length, MachineRepresentation::kTaggedSigned,
|
||||
SmiConstant(Smi::kZero));
|
||||
VARIABLE(result, MachineRepresentation::kTagged);
|
||||
|
||||
Node* string_encoding = Word32And(LoadInstanceType(current_string.value()),
|
||||
Int32Constant(kStringEncodingMask));
|
||||
|
||||
Label flat_length_loop(this, {®_index, ¤t_string, &total_length}),
|
||||
done_flat_length_loop(this);
|
||||
Goto(&flat_length_loop);
|
||||
BIND(&flat_length_loop);
|
||||
{
|
||||
Comment("Loop to find length and type of initial flat-string");
|
||||
Label is_sequential_or_can_deref(this);
|
||||
|
||||
// Increment total_length by the current string's length.
|
||||
Node* string_length = LoadStringLength(current_string.value());
|
||||
CSA_ASSERT(this, TaggedIsSmi(string_length));
|
||||
total_length.Bind(SmiAdd(total_length.value(), string_length));
|
||||
|
||||
// If we are above the min cons string length, bailout.
|
||||
GotoIf(SmiAboveOrEqual(total_length.value(),
|
||||
SmiConstant(ConsString::kMinLength)),
|
||||
&done_flat_length_loop);
|
||||
|
||||
// Check that all the strings have the same encoding type. If we got here
|
||||
// we are still under ConsString::kMinLength so need to bailout to the
|
||||
// runtime if the strings have different encodings.
|
||||
Node* instance_type = LoadInstanceType(current_string.value());
|
||||
GotoIf(Word32NotEqual(
|
||||
string_encoding,
|
||||
Word32And(instance_type, Int32Constant(kStringEncodingMask))),
|
||||
&call_runtime);
|
||||
|
||||
// Check if the new string is sequential or can be dereferenced as a
|
||||
// sequential string. If it can't and we've reached here, we are still under
|
||||
// ConsString::kMinLength so need to bailout to the runtime.
|
||||
GotoIf(IsSequentialStringInstanceType(instance_type),
|
||||
&is_sequential_or_can_deref);
|
||||
BranchIfCanDerefIndirectString(current_string.value(), instance_type,
|
||||
&is_sequential_or_can_deref, &call_runtime);
|
||||
BIND(&is_sequential_or_can_deref);
|
||||
|
||||
reg_index.Bind(NextRegister(reg_index.value()));
|
||||
GotoIf(IntPtrLessThan(reg_index.value(), last_reg_index),
|
||||
&done_flat_length_loop);
|
||||
current_string.Bind(LoadRegister(reg_index.value()));
|
||||
Goto(&flat_length_loop);
|
||||
}
|
||||
BIND(&done_flat_length_loop);
|
||||
|
||||
// If new length is greater than String::kMaxLength, goto runtime to throw.
|
||||
GotoIf(SmiAboveOrEqual(total_length.value(), SmiConstant(String::kMaxLength)),
|
||||
&call_runtime);
|
||||
|
||||
// If new length is less than ConsString::kMinLength, concatenate all operands
|
||||
// as a flat string.
|
||||
GotoIf(SmiLessThan(total_length.value(), SmiConstant(ConsString::kMinLength)),
|
||||
&do_flat_string);
|
||||
|
||||
// If the new length is is greater than ConsString::kMinLength, create a flat
|
||||
// string for first_reg_index to reg_index-1 if there is at least two strings
|
||||
// between.
|
||||
{
|
||||
Comment("New length is greater than ConsString::kMinLength");
|
||||
|
||||
// Subtract length of the last string that pushed us over the edge.
|
||||
Node* string_length = LoadStringLength(current_string.value());
|
||||
total_length.Bind(SmiSub(total_length.value(), string_length));
|
||||
|
||||
// If we have 2 or more operands under ConsString::kMinLength, concatenate
|
||||
// them as a flat string before concatenating the rest as a cons string. We
|
||||
// concatenate the initial string as a flat string even though we will end
|
||||
// up with a cons string since the time and memory overheads of that initial
|
||||
// flat string will be less than they would be for concatenating the whole
|
||||
// string as cons strings.
|
||||
GotoIf(
|
||||
IntPtrGreaterThanOrEqual(IntPtrSub(first_reg_index, reg_index.value()),
|
||||
IntPtrConstant(2)),
|
||||
&do_flat_string);
|
||||
|
||||
// Otherwise the whole concatenation should be cons strings.
|
||||
result.Bind(LoadRegister(first_reg_index));
|
||||
total_length.Bind(LoadStringLength(result.value()));
|
||||
reg_index.Bind(NextRegister(first_reg_index));
|
||||
Goto(&do_cons_string);
|
||||
}
|
||||
|
||||
BIND(&do_flat_string);
|
||||
{
|
||||
Comment("Flat string concatenation");
|
||||
Label two_byte(this);
|
||||
GotoIf(Word32Equal(string_encoding, Int32Constant(kTwoByteStringTag)),
|
||||
&two_byte);
|
||||
|
||||
{
|
||||
Comment("One-byte sequential string case");
|
||||
result.Bind(AllocateSeqOneByteString(context, total_length.value(),
|
||||
SMI_PARAMETERS));
|
||||
CopySequentialStrings(result.value(), first_reg_index, reg_index.value(),
|
||||
total_length.value(), String::ONE_BYTE_ENCODING);
|
||||
// If there is still more registers to concatenate, jump to the cons
|
||||
// string case, otherwise we are done.
|
||||
Branch(IntPtrLessThan(reg_index.value(), last_reg_index), &done_native,
|
||||
&do_cons_string);
|
||||
}
|
||||
|
||||
BIND(&two_byte);
|
||||
{
|
||||
Comment("Two-byte sequential string case");
|
||||
result.Bind(AllocateSeqTwoByteString(context, total_length.value(),
|
||||
SMI_PARAMETERS));
|
||||
CopySequentialStrings(result.value(), first_reg_index, reg_index.value(),
|
||||
total_length.value(), String::TWO_BYTE_ENCODING);
|
||||
// If there is still more registers to concatenate, jump to the cons
|
||||
// string case, otherwise we are done.
|
||||
Branch(IntPtrLessThan(reg_index.value(), last_reg_index), &done_native,
|
||||
&do_cons_string);
|
||||
}
|
||||
}
|
||||
|
||||
BIND(&do_cons_string);
|
||||
{
|
||||
Comment("Create cons string");
|
||||
Label loop(this, {®_index, &total_length, &result}), done_loop(this),
|
||||
done_cons(this);
|
||||
|
||||
Goto(&loop);
|
||||
BIND(&loop);
|
||||
{
|
||||
Node* next_string = LoadRegister(reg_index.value());
|
||||
Node* next_length = LoadStringLength(next_string);
|
||||
|
||||
// Skip concatenating empty string.
|
||||
GotoIf(SmiEqual(next_length, SmiConstant(Smi::kZero)), &done_cons);
|
||||
|
||||
total_length.Bind(SmiAdd(total_length.value(), next_length));
|
||||
|
||||
// If new length is greater than String::kMaxLength, goto runtime to
|
||||
// throw. Note: we also need to invalidate the string length protector, so
|
||||
// can't just throw here directly.
|
||||
GotoIf(SmiAboveOrEqual(total_length.value(),
|
||||
SmiConstant(String::kMaxLength)),
|
||||
&call_runtime);
|
||||
|
||||
result.Bind(NewConsString(context, total_length.value(), result.value(),
|
||||
next_string, CodeStubAssembler::kNone));
|
||||
Goto(&done_cons);
|
||||
|
||||
BIND(&done_cons);
|
||||
reg_index.Bind(NextRegister(reg_index.value()));
|
||||
GotoIfNot(IntPtrLessThan(reg_index.value(), last_reg_index), &loop);
|
||||
Goto(&done_loop);
|
||||
}
|
||||
|
||||
BIND(&done_loop);
|
||||
Goto(&done_native);
|
||||
}
|
||||
StringBuiltinsAssembler string_assembler(state());
|
||||
result.Bind(string_assembler.ConcatenateStrings(context, first_reg_ptr,
|
||||
reg_count, &call_runtime));
|
||||
Goto(&done);
|
||||
|
||||
BIND(&call_runtime);
|
||||
{
|
||||
Comment("Call runtime.");
|
||||
Node* runtime_id = Int32Constant(Runtime::kInterpreterStringConcat);
|
||||
Node* first_reg_loc = RegisterLocation(first_reg_index);
|
||||
result.Bind(CallRuntimeN(runtime_id, context, first_reg_loc, reg_count));
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
BIND(&done_native);
|
||||
{
|
||||
IncrementCounter(isolate()->counters()->string_add_native(), 1);
|
||||
Node* runtime_id = Int32Constant(Runtime::kStringConcat);
|
||||
result.Bind(CallRuntimeN(runtime_id, context, first_reg_ptr, reg_count));
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
@ -1770,10 +1520,6 @@ IGNITION_HANDLER(StringConcat, InterpreterStringConcatAssembler) {
|
||||
//
|
||||
// Increments value in the accumulator by one.
|
||||
IGNITION_HANDLER(Inc, InterpreterAssembler) {
|
||||
typedef CodeStubAssembler::Label Label;
|
||||
typedef compiler::Node Node;
|
||||
typedef CodeStubAssembler::Variable Variable;
|
||||
|
||||
Node* value = GetAccumulator();
|
||||
Node* context = GetContext();
|
||||
Node* slot_index = BytecodeOperandIdx(0);
|
||||
@ -1897,10 +1643,6 @@ IGNITION_HANDLER(Inc, InterpreterAssembler) {
|
||||
//
|
||||
// Decrements value in the accumulator by one.
|
||||
IGNITION_HANDLER(Dec, InterpreterAssembler) {
|
||||
typedef CodeStubAssembler::Label Label;
|
||||
typedef compiler::Node Node;
|
||||
typedef CodeStubAssembler::Variable Variable;
|
||||
|
||||
Node* value = GetAccumulator();
|
||||
Node* context = GetContext();
|
||||
Node* slot_index = BytecodeOperandIdx(0);
|
||||
|
@ -47,6 +47,8 @@ const Register StoreTransitionDescriptor::MapRegister() { return t1; }
|
||||
const Register StringCompareDescriptor::LeftRegister() { return a1; }
|
||||
const Register StringCompareDescriptor::RightRegister() { return a0; }
|
||||
|
||||
const Register StringConcatDescriptor::ArgumentsCountRegister() { return a0; }
|
||||
|
||||
const Register ApiGetterDescriptor::HolderRegister() { return a0; }
|
||||
const Register ApiGetterDescriptor::CallbackRegister() { return a3; }
|
||||
|
||||
|
@ -47,6 +47,8 @@ const Register StoreTransitionDescriptor::MapRegister() { return a5; }
|
||||
const Register StringCompareDescriptor::LeftRegister() { return a1; }
|
||||
const Register StringCompareDescriptor::RightRegister() { return a0; }
|
||||
|
||||
const Register StringConcatDescriptor::ArgumentsCountRegister() { return a0; }
|
||||
|
||||
const Register ApiGetterDescriptor::HolderRegister() { return a0; }
|
||||
const Register ApiGetterDescriptor::CallbackRegister() { return a3; }
|
||||
|
||||
|
@ -47,6 +47,8 @@ const Register StoreTransitionDescriptor::MapRegister() { return r8; }
|
||||
const Register StringCompareDescriptor::LeftRegister() { return r4; }
|
||||
const Register StringCompareDescriptor::RightRegister() { return r3; }
|
||||
|
||||
const Register StringConcatDescriptor::ArgumentsCountRegister() { return r3; }
|
||||
|
||||
const Register ApiGetterDescriptor::HolderRegister() { return r3; }
|
||||
const Register ApiGetterDescriptor::CallbackRegister() { return r6; }
|
||||
|
||||
|
@ -35,23 +35,6 @@ RUNTIME_FUNCTION(Runtime_InterpreterNewClosure) {
|
||||
static_cast<PretenureFlag>(pretenured_flag));
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_InterpreterStringConcat) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_LE(2, args.length());
|
||||
int const argc = args.length();
|
||||
ScopedVector<Handle<Object>> argv(argc);
|
||||
|
||||
isolate->counters()->string_add_runtime()->Increment();
|
||||
IncrementalStringBuilder builder(isolate);
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
Handle<String> str = Handle<String>::cast(args.at(i));
|
||||
if (str->length() != 0) {
|
||||
builder.AppendString(str);
|
||||
}
|
||||
}
|
||||
RETURN_RESULT_OR_FAILURE(isolate, builder.Finish());
|
||||
}
|
||||
|
||||
#ifdef V8_TRACE_IGNITION
|
||||
|
||||
namespace {
|
||||
|
@ -108,7 +108,6 @@ MaybeHandle<String> StringReplaceOneCharWithString(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_StringReplaceOneCharWithString) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(3, args.length());
|
||||
@ -197,7 +196,6 @@ RUNTIME_FUNCTION(Runtime_SubString) {
|
||||
return *isolate->factory()->NewSubString(string, start, end);
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_StringAdd) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(2, args.length());
|
||||
@ -208,6 +206,22 @@ RUNTIME_FUNCTION(Runtime_StringAdd) {
|
||||
isolate->factory()->NewConsString(str1, str2));
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_StringConcat) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_LE(2, args.length());
|
||||
int const argc = args.length();
|
||||
ScopedVector<Handle<Object>> argv(argc);
|
||||
|
||||
isolate->counters()->string_add_runtime()->Increment();
|
||||
IncrementalStringBuilder builder(isolate);
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
Handle<String> str = Handle<String>::cast(args.at(i));
|
||||
if (str->length() != 0) {
|
||||
builder.AppendString(str);
|
||||
}
|
||||
}
|
||||
RETURN_RESULT_OR_FAILURE(isolate, builder.Finish());
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_InternalizeString) {
|
||||
HandleScope handles(isolate);
|
||||
@ -216,7 +230,6 @@ RUNTIME_FUNCTION(Runtime_InternalizeString) {
|
||||
return *isolate->factory()->InternalizeString(string);
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_StringCharCodeAtRT) {
|
||||
HandleScope handle_scope(isolate);
|
||||
DCHECK_EQ(2, args.length());
|
||||
@ -236,7 +249,6 @@ RUNTIME_FUNCTION(Runtime_StringCharCodeAtRT) {
|
||||
return Smi::FromInt(subject->Get(i));
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_StringCompare) {
|
||||
HandleScope handle_scope(isolate);
|
||||
DCHECK_EQ(2, args.length());
|
||||
@ -256,7 +268,6 @@ RUNTIME_FUNCTION(Runtime_StringCompare) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_StringBuilderConcat) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(3, args.length());
|
||||
@ -329,7 +340,6 @@ RUNTIME_FUNCTION(Runtime_StringBuilderConcat) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_StringBuilderJoin) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(3, args.length());
|
||||
@ -470,7 +480,6 @@ static void JoinSparseArrayWithSeparator(FixedArray* elements,
|
||||
DCHECK(cursor <= buffer.length());
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_SparseJoinWithSeparator) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(3, args.length());
|
||||
@ -555,7 +564,6 @@ RUNTIME_FUNCTION(Runtime_SparseJoinWithSeparator) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Copies Latin1 characters to the given fixed array looking up
|
||||
// one-char strings in the cache. Gives up on the first char that is
|
||||
// not in the cache and fills the remainder with smi zeros. Returns
|
||||
@ -586,7 +594,6 @@ static int CopyCachedOneByteCharsToArray(Heap* heap, const uint8_t* chars,
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
// Converts a String to JSArray.
|
||||
// For example, "foo" => ["f", "o", "o"].
|
||||
RUNTIME_FUNCTION(Runtime_StringToArray) {
|
||||
@ -634,7 +641,6 @@ RUNTIME_FUNCTION(Runtime_StringToArray) {
|
||||
return *isolate->factory()->NewJSArrayWithElements(elements);
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_StringLessThan) {
|
||||
HandleScope handle_scope(isolate);
|
||||
DCHECK_EQ(2, args.length());
|
||||
@ -726,7 +732,6 @@ RUNTIME_FUNCTION(Runtime_FlattenString) {
|
||||
return *String::Flatten(str);
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_StringCharFromCode) {
|
||||
HandleScope handlescope(isolate);
|
||||
DCHECK_EQ(1, args.length());
|
||||
|
@ -229,7 +229,6 @@ namespace internal {
|
||||
#define FOR_EACH_INTRINSIC_INTERPRETER(F) \
|
||||
FOR_EACH_INTRINSIC_INTERPRETER_TRACE(F) \
|
||||
F(InterpreterNewClosure, 4, 1) \
|
||||
F(InterpreterStringConcat, -1 /* >= 2 */, 1) \
|
||||
F(InterpreterAdvanceBytecodeOffset, 2, 1)
|
||||
|
||||
#define FOR_EACH_INTRINSIC_FUNCTION(F) \
|
||||
@ -542,6 +541,7 @@ namespace internal {
|
||||
F(StringLastIndexOf, 2, 1) \
|
||||
F(SubString, 3, 1) \
|
||||
F(StringAdd, 2, 1) \
|
||||
F(StringConcat, -1 /* >= 2 */, 1) \
|
||||
F(InternalizeString, 1, 1) \
|
||||
F(StringCharCodeAtRT, 2, 1) \
|
||||
F(StringCompare, 2, 1) \
|
||||
|
@ -47,6 +47,8 @@ const Register StoreTransitionDescriptor::MapRegister() { return r7; }
|
||||
const Register StringCompareDescriptor::LeftRegister() { return r3; }
|
||||
const Register StringCompareDescriptor::RightRegister() { return r2; }
|
||||
|
||||
const Register StringConcatDescriptor::ArgumentsCountRegister() { return r2; }
|
||||
|
||||
const Register ApiGetterDescriptor::HolderRegister() { return r2; }
|
||||
const Register ApiGetterDescriptor::CallbackRegister() { return r5; }
|
||||
|
||||
|
@ -186,6 +186,7 @@
|
||||
'builtins/builtins-constructor-gen.h',
|
||||
'builtins/builtins-constructor.h',
|
||||
'builtins/builtins-conversion-gen.cc',
|
||||
'builtins/builtins-conversion-gen.h',
|
||||
'builtins/builtins-date-gen.cc',
|
||||
'builtins/builtins-debug-gen.cc',
|
||||
'builtins/builtins-forin-gen.cc',
|
||||
|
@ -47,6 +47,8 @@ const Register StoreTransitionDescriptor::MapRegister() { return r11; }
|
||||
const Register StringCompareDescriptor::LeftRegister() { return rdx; }
|
||||
const Register StringCompareDescriptor::RightRegister() { return rax; }
|
||||
|
||||
const Register StringConcatDescriptor::ArgumentsCountRegister() { return rbx; }
|
||||
|
||||
const Register ApiGetterDescriptor::HolderRegister() { return rcx; }
|
||||
const Register ApiGetterDescriptor::CallbackRegister() { return rbx; }
|
||||
|
||||
|
@ -47,6 +47,8 @@ const Register StoreTransitionDescriptor::MapRegister() { return edi; }
|
||||
const Register StringCompareDescriptor::LeftRegister() { return edx; }
|
||||
const Register StringCompareDescriptor::RightRegister() { return eax; }
|
||||
|
||||
const Register StringConcatDescriptor::ArgumentsCountRegister() { return eax; }
|
||||
|
||||
const Register ApiGetterDescriptor::HolderRegister() { return ecx; }
|
||||
const Register ApiGetterDescriptor::CallbackRegister() { return eax; }
|
||||
|
||||
|
@ -44,7 +44,6 @@ static MaybeHandle<Object> CallFunction(Isolate* isolate,
|
||||
isolate->factory()->undefined_value(), 0, nullptr);
|
||||
}
|
||||
|
||||
|
||||
template <class... A>
|
||||
static MaybeHandle<Object> CallFunction(Isolate* isolate,
|
||||
Handle<JSFunction> function,
|
||||
@ -55,7 +54,6 @@ static MaybeHandle<Object> CallFunction(Isolate* isolate,
|
||||
argv);
|
||||
}
|
||||
|
||||
|
||||
template <class... A>
|
||||
class BytecodeGraphCallable {
|
||||
public:
|
||||
@ -72,7 +70,6 @@ class BytecodeGraphCallable {
|
||||
Handle<JSFunction> function_;
|
||||
};
|
||||
|
||||
|
||||
class BytecodeGraphTester {
|
||||
public:
|
||||
BytecodeGraphTester(Isolate* isolate, const char* script,
|
||||
@ -139,7 +136,6 @@ class BytecodeGraphTester {
|
||||
DISALLOW_COPY_AND_ASSIGN(BytecodeGraphTester);
|
||||
};
|
||||
|
||||
|
||||
#define SPACE()
|
||||
|
||||
#define REPEAT_2(SEP, ...) __VA_ARGS__ SEP() __VA_ARGS__
|
||||
@ -169,7 +165,6 @@ class BytecodeGraphTester {
|
||||
SEP() \
|
||||
REPEAT_4(SEP, __VA_ARGS__) SEP() REPEAT_2(SEP, __VA_ARGS__) SEP() __VA_ARGS__
|
||||
|
||||
|
||||
template <int N, typename T = Handle<Object>>
|
||||
struct ExpectedSnippet {
|
||||
const char* code_snippet;
|
||||
@ -184,7 +179,6 @@ struct ExpectedSnippet {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderReturnStatements) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -219,7 +213,6 @@ TEST(BytecodeGraphBuilderReturnStatements) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderPrimitiveExpressions) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -245,7 +238,6 @@ TEST(BytecodeGraphBuilderPrimitiveExpressions) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderTwoParameterTests) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -304,6 +296,103 @@ TEST(BytecodeGraphBuilderTwoParameterTests) {
|
||||
}
|
||||
}
|
||||
|
||||
class OneByteResource : public v8::String::ExternalOneByteStringResource {
|
||||
public:
|
||||
OneByteResource(const char* data, size_t length)
|
||||
: data_(data), length_(length) {}
|
||||
~OneByteResource() { i::DeleteArray(data_); }
|
||||
virtual const char* data() const { return data_; }
|
||||
virtual size_t length() const { return length_; }
|
||||
|
||||
private:
|
||||
const char* data_;
|
||||
size_t length_;
|
||||
};
|
||||
|
||||
TEST(BytecodeGraphBuilderStringConcat) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
v8::Isolate* ext_isolate = reinterpret_cast<v8::Isolate*>(isolate);
|
||||
Factory* factory = isolate->factory();
|
||||
|
||||
uc16 array1[] = {2001, 2002, 2003};
|
||||
Vector<const uc16> two_byte_str_1(array1);
|
||||
|
||||
uc16 array2[] = {1001, 1002, 1003, 1004, 1005, 1006};
|
||||
Vector<const uc16> two_byte_str_2(array2);
|
||||
|
||||
uc16 array3[] = {1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009};
|
||||
Vector<const uc16> two_byte_str_3(array3);
|
||||
|
||||
OneByteResource* external_resource =
|
||||
new OneByteResource(StrDup("external"), 8);
|
||||
v8::Local<v8::String> ext_string_local =
|
||||
v8::String::NewExternalOneByte(ext_isolate, external_resource)
|
||||
.ToLocalChecked();
|
||||
Handle<String> external_string = v8::Utils::OpenHandle(*ext_string_local);
|
||||
|
||||
Handle<String> thin_string =
|
||||
factory->NewStringFromAsciiChecked("thin_string");
|
||||
factory->InternalizeString(thin_string);
|
||||
DCHECK_IMPLIES(FLAG_thin_strings, thin_string->IsThinString());
|
||||
|
||||
Handle<String> sliced_string =
|
||||
factory->NewStringFromAsciiChecked("string that is going to be sliced");
|
||||
sliced_string = factory->NewProperSubString(sliced_string, 2, 27);
|
||||
DCHECK_IMPLIES(FLAG_string_slices, sliced_string->IsSlicedString());
|
||||
|
||||
Handle<String> inputs[] = {
|
||||
factory->NewStringFromAsciiChecked(""),
|
||||
factory->NewStringFromAsciiChecked("a"),
|
||||
factory->NewStringFromAsciiChecked("abc"),
|
||||
factory->NewStringFromAsciiChecked("underconsmin"),
|
||||
factory->NewStringFromAsciiChecked("long string over cons min length"),
|
||||
factory->NewStringFromTwoByte(two_byte_str_1).ToHandleChecked(),
|
||||
factory->NewStringFromTwoByte(two_byte_str_2).ToHandleChecked(),
|
||||
factory->NewStringFromTwoByte(two_byte_str_3).ToHandleChecked(),
|
||||
factory
|
||||
->NewConsString(factory->NewStringFromAsciiChecked("foo"),
|
||||
factory->NewStringFromAsciiChecked("bar"))
|
||||
.ToHandleChecked(),
|
||||
factory
|
||||
->NewConsString(factory->empty_string(),
|
||||
factory->NewStringFromAsciiChecked("bar"))
|
||||
.ToHandleChecked(),
|
||||
factory
|
||||
->NewConsString(factory->NewStringFromAsciiChecked("foo"),
|
||||
factory->empty_string())
|
||||
.ToHandleChecked(),
|
||||
external_string,
|
||||
thin_string,
|
||||
sliced_string,
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < arraysize(inputs); i++) {
|
||||
for (size_t j = 0; j < arraysize(inputs); j++) {
|
||||
ScopedVector<char> script(1024);
|
||||
SNPrintF(script,
|
||||
"function %s(p1, p2) { return 'abc' + p1 + p2; }\n;"
|
||||
"%s('a', 'b');",
|
||||
kFunctionName, kFunctionName);
|
||||
|
||||
BytecodeGraphTester tester(isolate, script.start());
|
||||
auto callable = tester.GetCallable<Handle<Object>, Handle<Object>>();
|
||||
Handle<Object> return_value =
|
||||
callable(inputs[i], inputs[j]).ToHandleChecked();
|
||||
|
||||
Handle<String> expected =
|
||||
factory
|
||||
->NewConsString(
|
||||
factory
|
||||
->NewConsString(factory->NewStringFromAsciiChecked("abc"),
|
||||
inputs[i])
|
||||
.ToHandleChecked(),
|
||||
inputs[j])
|
||||
.ToHandleChecked();
|
||||
CHECK(return_value->SameValue(*expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BytecodeGraphBuilderNamedLoad) {
|
||||
HandleAndZoneScope scope;
|
||||
@ -346,7 +435,6 @@ TEST(BytecodeGraphBuilderNamedLoad) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderKeyedLoad) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -542,7 +630,6 @@ TEST(BytecodeGraphBuilderPropertyCall) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderCallNew) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -579,7 +666,6 @@ TEST(BytecodeGraphBuilderCallNew) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderCreateClosure) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -616,7 +702,6 @@ TEST(BytecodeGraphBuilderCreateClosure) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderCallRuntime) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -718,7 +803,6 @@ TEST(BytecodeGraphBuilderToObject) {
|
||||
// TODO(mythria): tests for ToObject. Needs ForIn.
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderToName) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -761,7 +845,6 @@ TEST(BytecodeGraphBuilderToName) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderLogicalNot) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -792,7 +875,6 @@ TEST(BytecodeGraphBuilderLogicalNot) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderTypeOf) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -924,7 +1006,6 @@ TEST(BytecodeGraphBuilderCountOperation) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderDelete) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -962,7 +1043,6 @@ TEST(BytecodeGraphBuilderDelete) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderDeleteGlobal) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1013,7 +1093,6 @@ TEST(BytecodeGraphBuilderDeleteGlobal) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderDeleteLookupSlot) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1049,7 +1128,6 @@ TEST(BytecodeGraphBuilderDeleteLookupSlot) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderLookupSlot) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1228,7 +1306,6 @@ TEST(BytecodeGraphBuilderLookupSlotWide) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderCallLookupSlot) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1256,7 +1333,6 @@ TEST(BytecodeGraphBuilderCallLookupSlot) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderEval) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1307,7 +1383,6 @@ TEST(BytecodeGraphBuilderEval) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderEvalParams) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1335,7 +1410,6 @@ TEST(BytecodeGraphBuilderEvalParams) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderEvalGlobal) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1361,7 +1435,6 @@ TEST(BytecodeGraphBuilderEvalGlobal) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool get_compare_result(Token::Value opcode, Handle<Object> lhs_value,
|
||||
Handle<Object> rhs_value) {
|
||||
switch (opcode) {
|
||||
@ -1386,7 +1459,6 @@ bool get_compare_result(Token::Value opcode, Handle<Object> lhs_value,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char* get_code_snippet(Token::Value opcode) {
|
||||
switch (opcode) {
|
||||
case Token::Value::EQ:
|
||||
@ -1410,7 +1482,6 @@ const char* get_code_snippet(Token::Value opcode) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderCompare) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1446,7 +1517,6 @@ TEST(BytecodeGraphBuilderCompare) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderTestIn) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1494,7 +1564,6 @@ TEST(BytecodeGraphBuilderTestIn) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderTestInstanceOf) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1642,7 +1711,6 @@ TEST(BytecodeGraphBuilderThrow) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderContext) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1700,7 +1768,6 @@ TEST(BytecodeGraphBuilderContext) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderLoadContext) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1765,7 +1832,6 @@ TEST(BytecodeGraphBuilderLoadContext) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderCreateArgumentsNoParameters) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1795,7 +1861,6 @@ TEST(BytecodeGraphBuilderCreateArgumentsNoParameters) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderCreateArguments) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1917,7 +1982,6 @@ TEST(BytecodeGraphBuilderRegExpLiterals) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderArrayLiterals) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -1956,7 +2020,6 @@ TEST(BytecodeGraphBuilderArrayLiterals) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderObjectLiterals) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -2019,7 +2082,6 @@ TEST(BytecodeGraphBuilderObjectLiterals) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderIf) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -2128,7 +2190,6 @@ TEST(BytecodeGraphBuilderIf) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderConditionalOperator) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -2158,7 +2219,6 @@ TEST(BytecodeGraphBuilderConditionalOperator) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderSwitch) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -2312,7 +2372,6 @@ TEST(BytecodeGraphBuilderNestedSwitch) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderBreakableBlocks) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -2352,7 +2411,6 @@ TEST(BytecodeGraphBuilderBreakableBlocks) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderWhile) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -2400,7 +2458,6 @@ TEST(BytecodeGraphBuilderWhile) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderDo) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -2448,7 +2505,6 @@ TEST(BytecodeGraphBuilderDo) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderFor) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -2541,7 +2597,6 @@ TEST(BytecodeGraphBuilderFor) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderForIn) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
@ -2612,7 +2667,6 @@ TEST(BytecodeGraphBuilderForIn) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderForOf) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
|
@ -42,6 +42,7 @@ const SharedOperator kSharedOperators[] = {
|
||||
}
|
||||
SHARED(ToNumber, Operator::kNoProperties, 1, 1, 1, 1, 1, 1, 2),
|
||||
SHARED(ToString, Operator::kNoProperties, 1, 1, 1, 1, 1, 1, 2),
|
||||
SHARED(ToPrimitiveToString, Operator::kNoProperties, 1, 1, 1, 1, 1, 1, 2),
|
||||
SHARED(ToName, Operator::kNoProperties, 1, 1, 1, 1, 1, 1, 2),
|
||||
SHARED(ToObject, Operator::kFoldable, 1, 1, 1, 1, 1, 1, 2),
|
||||
SHARED(Create, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
|
||||
|
@ -250,11 +250,27 @@ TEST_F(JSTypedLoweringTest, JSToStringWithBoolean) {
|
||||
IsHeapConstant(factory()->false_string())));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// JSToPrimitiveToString
|
||||
|
||||
TEST_F(JSTypedLoweringTest, JSToPrimitiveToStringWithBoolean) {
|
||||
Node* const input = Parameter(Type::Boolean(), 0);
|
||||
Node* const context = Parameter(Type::Any(), 1);
|
||||
Node* const frame_state = EmptyFrameState();
|
||||
Node* const effect = graph()->start();
|
||||
Node* const control = graph()->start();
|
||||
Reduction r = Reduce(graph()->NewNode(javascript()->ToString(), input,
|
||||
context, frame_state, effect, control));
|
||||
ASSERT_TRUE(r.Changed());
|
||||
EXPECT_THAT(r.replacement(),
|
||||
IsSelect(MachineRepresentation::kTagged, input,
|
||||
IsHeapConstant(factory()->true_string()),
|
||||
IsHeapConstant(factory()->false_string())));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// JSStrictEqual
|
||||
|
||||
|
||||
TEST_F(JSTypedLoweringTest, JSStrictEqualWithTheHole) {
|
||||
Node* const the_hole = HeapConstant(factory()->the_hole_value());
|
||||
Node* const context = UndefinedConstant();
|
||||
|
Loading…
Reference in New Issue
Block a user