[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:
Ross McIlroy 2017-06-07 12:12:49 +01:00 committed by Commit Bot
parent 074b0464e2
commit fdfb8c9efb
41 changed files with 729 additions and 379 deletions

View File

@ -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",

View File

@ -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; }

View File

@ -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; }

View File

@ -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) {

View 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_

View File

@ -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) \

View File

@ -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, {&current_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, &current_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(&current_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, {&current_arg, &current_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, {&current_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

View File

@ -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,

View File

@ -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) {

View File

@ -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_;

View File

@ -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(); }

View File

@ -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( \

View File

@ -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());

View File

@ -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());

View File

@ -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

View File

@ -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);

View File

@ -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:

View File

@ -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);

View File

@ -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) \

View File

@ -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:

View File

@ -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);

View File

@ -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.

View File

@ -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;

View File

@ -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:

View File

@ -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; }

View File

@ -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()};

View File

@ -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)

View File

@ -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, {&copy_reg_index, &copy_str_index}),
done_copy_loop(this);
Goto(&copy_loop);
BIND(&copy_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),
&copy_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, {&reg_index, &current_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, {&reg_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);

View File

@ -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; }

View File

@ -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; }

View File

@ -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; }

View File

@ -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 {

View File

@ -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());

View File

@ -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) \

View File

@ -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; }

View File

@ -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',

View File

@ -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; }

View File

@ -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; }

View File

@ -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();

View File

@ -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),

View File

@ -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();