From fdfb8c9efbbd96cde5e4cc09cd22146d75e5598b Mon Sep 17 00:00:00 2001 From: Ross McIlroy Date: Wed, 7 Jun 2017 12:12:49 +0100 Subject: [PATCH] [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 Reviewed-by: Jaroslav Sevcik Reviewed-by: Jakob Gruber Cr-Commit-Position: refs/heads/master@{#45756} --- BUILD.gn | 1 + src/arm/interface-descriptors-arm.cc | 2 + src/arm64/interface-descriptors-arm64.cc | 2 + src/builtins/builtins-conversion-gen.cc | 63 +++- src/builtins/builtins-conversion-gen.h | 32 ++ src/builtins/builtins-definitions.h | 2 + src/builtins/builtins-string-gen.cc | 253 +++++++++++++++ src/builtins/builtins-string-gen.h | 8 + src/code-stub-assembler.cc | 22 +- src/code-stub-assembler.h | 17 +- src/compiler/bytecode-graph-builder.cc | 21 +- src/compiler/code-assembler.cc | 16 + src/compiler/code-assembler.h | 5 + src/compiler/js-generic-lowering.cc | 15 +- src/compiler/js-operator.cc | 34 +- src/compiler/js-operator.h | 24 ++ src/compiler/js-typed-lowering.cc | 20 ++ src/compiler/js-typed-lowering.h | 1 + src/compiler/opcodes.h | 4 +- src/compiler/operator-properties.cc | 1 + src/compiler/simplified-lowering.cc | 1 + src/compiler/typer.cc | 17 + src/compiler/verifier.cc | 11 + src/debug/debug-evaluate.cc | 1 + src/ia32/interface-descriptors-ia32.cc | 2 + src/interface-descriptors.cc | 14 + src/interface-descriptors.h | 11 +- src/interpreter/interpreter-generator.cc | 290 +----------------- src/mips/interface-descriptors-mips.cc | 2 + src/mips64/interface-descriptors-mips64.cc | 2 + src/ppc/interface-descriptors-ppc.cc | 2 + src/runtime/runtime-interpreter.cc | 17 - src/runtime/runtime-strings.cc | 27 +- src/runtime/runtime.h | 2 +- src/s390/interface-descriptors-s390.cc | 2 + src/v8.gyp | 1 + src/x64/interface-descriptors-x64.cc | 2 + src/x87/interface-descriptors-x87.cc | 2 + .../test-run-bytecode-graph-builder.cc | 140 ++++++--- .../compiler/js-operator-unittest.cc | 1 + .../compiler/js-typed-lowering-unittest.cc | 18 +- 41 files changed, 729 insertions(+), 379 deletions(-) create mode 100644 src/builtins/builtins-conversion-gen.h diff --git a/BUILD.gn b/BUILD.gn index 104c0ac975..496db38441 100644 --- a/BUILD.gn +++ b/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", diff --git a/src/arm/interface-descriptors-arm.cc b/src/arm/interface-descriptors-arm.cc index f2fb703b9f..04c7cf044f 100644 --- a/src/arm/interface-descriptors-arm.cc +++ b/src/arm/interface-descriptors-arm.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; } diff --git a/src/arm64/interface-descriptors-arm64.cc b/src/arm64/interface-descriptors-arm64.cc index 887adddf29..5bc58d7bcd 100644 --- a/src/arm64/interface-descriptors-arm64.cc +++ b/src/arm64/interface-descriptors-arm64.cc @@ -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; } diff --git a/src/builtins/builtins-conversion-gen.cc b/src/builtins/builtins-conversion-gen.cc index 5fe2cb03bd..7233aef8b5 100644 --- a/src/builtins/builtins-conversion-gen.cc +++ b/src/builtins/builtins-conversion-gen.cc @@ -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) { diff --git a/src/builtins/builtins-conversion-gen.h b/src/builtins/builtins-conversion-gen.h new file mode 100644 index 0000000000..fedbc54d2e --- /dev/null +++ b/src/builtins/builtins-conversion-gen.h @@ -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_ diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 7346f66686..1e8f2e9ed7 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.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) \ diff --git a/src/builtins/builtins-string-gen.cc b/src/builtins/builtins-string-gen.cc index f56a5e8467..9138f1da7c 100644 --- a/src/builtins/builtins-string-gen.cc +++ b/src/builtins/builtins-string-gen.cc @@ -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 diff --git a/src/builtins/builtins-string-gen.h b/src/builtins/builtins-string-gen.h index 399f565e55..42e3e6c71d 100644 --- a/src/builtins/builtins-string-gen.h +++ b/src/builtins/builtins-string-gen.h @@ -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, diff --git a/src/code-stub-assembler.cc b/src/code-stub-assembler.cc index 85e080325d..657f5c31c5 100644 --- a/src/code-stub-assembler.cc +++ b/src/code-stub-assembler.cc @@ -9175,21 +9175,23 @@ Node* CodeStubAssembler::IsDetachedBuffer(Node* buffer) { return IsSetWord32(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) { diff --git a/src/code-stub-assembler.h b/src/code-stub-assembler.h index 74da870d68..e9672a8f4b 100644 --- a/src/code-stub-assembler.h +++ b/src/code-stub-assembler.h @@ -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_; diff --git a/src/compiler/bytecode-graph-builder.cc b/src/compiler/bytecode-graph-builder.cc index e3352df655..63083b3604 100644 --- a/src/compiler/bytecode-graph-builder.cc +++ b/src/compiler/bytecode-graph-builder.cc @@ -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(bytecode_iterator().GetRegisterCountOperand(1)); + Node** operands = + local_zone()->NewArray(static_cast(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(); } diff --git a/src/compiler/code-assembler.cc b/src/compiler/code-assembler.cc index 9277823889..1bfd72a63e 100644 --- a/src/compiler/code-assembler.cc +++ b/src/compiler/code-assembler.cc @@ -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(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( \ diff --git a/src/compiler/code-assembler.h b/src/compiler/code-assembler.h index 1f2e4d8f4f..2123b76ad1 100644 --- a/src/compiler/code-assembler.h +++ b/src/compiler/code-assembler.h @@ -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 Node* CallStub(Callable const& callable, Node* context, TArgs... args) { Node* target = HeapConstant(callable.code()); diff --git a/src/compiler/js-generic-lowering.cc b/src/compiler/js-generic-lowering.cc index fb191e96b4..d86c975e8a 100644 --- a/src/compiler/js-generic-lowering.cc +++ b/src/compiler/js-generic-lowering.cc @@ -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()); diff --git a/src/compiler/js-operator.cc b/src/compiler/js-operator.cc index 4895d4bf41..4da32a9b73 100644 --- a/src/compiler/js-operator.cc +++ b/src/compiler/js-operator.cc @@ -116,6 +116,29 @@ SpreadWithArityParameter const& SpreadWithArityParameterOf(Operator const* op) { return OpParameter(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(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( // -- + 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 site) { // constructor, new_target, arg1, ..., argN diff --git a/src/compiler/js-operator.h b/src/compiler/js-operator.h index a3ea21a28b..b022d01f86 100644 --- a/src/compiler/js-operator.h +++ b/src/compiler/js-operator.h @@ -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); diff --git a/src/compiler/js-typed-lowering.cc b/src/compiler/js-typed-lowering.cc index 2ad484dc2f..cf16ba3d02 100644 --- a/src/compiler/js-typed-lowering.cc +++ b/src/compiler/js-typed-lowering.cc @@ -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: diff --git a/src/compiler/js-typed-lowering.h b/src/compiler/js-typed-lowering.h index 0b92a40a5b..94714863f0 100644 --- a/src/compiler/js-typed-lowering.h +++ b/src/compiler/js-typed-lowering.h @@ -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); diff --git a/src/compiler/opcodes.h b/src/compiler/opcodes.h index 39cf2fae31..b543ffbc66 100644 --- a/src/compiler/opcodes.h +++ b/src/compiler/opcodes.h @@ -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) \ diff --git a/src/compiler/operator-properties.cc b/src/compiler/operator-properties.cc index 35b24d8531..26693feb8c 100644 --- a/src/compiler/operator-properties.cc +++ b/src/compiler/operator-properties.cc @@ -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: diff --git a/src/compiler/simplified-lowering.cc b/src/compiler/simplified-lowering.cc index 06bc109348..bc65a48eeb 100644 --- a/src/compiler/simplified-lowering.cc +++ b/src/compiler/simplified-lowering.cc @@ -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); diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc index 14ca0cc46b..580912cc40 100644 --- a/src/compiler/typer.cc +++ b/src/compiler/typer.cc @@ -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. diff --git a/src/compiler/verifier.cc b/src/compiler/verifier.cc index fa4e14bfae..854feaeb94 100644 --- a/src/compiler/verifier.cc +++ b/src/compiler/verifier.cc @@ -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; diff --git a/src/debug/debug-evaluate.cc b/src/debug/debug-evaluate.cc index 0a4cfb1bb7..c4ba7d21e2 100644 --- a/src/debug/debug-evaluate.cc +++ b/src/debug/debug-evaluate.cc @@ -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: diff --git a/src/ia32/interface-descriptors-ia32.cc b/src/ia32/interface-descriptors-ia32.cc index b8547d0194..ac2b1402ab 100644 --- a/src/ia32/interface-descriptors-ia32.cc +++ b/src/ia32/interface-descriptors-ia32.cc @@ -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; } diff --git a/src/interface-descriptors.cc b/src/interface-descriptors.cc index f44ee619d9..f30d6fadeb 100644 --- a/src/interface-descriptors.cc +++ b/src/interface-descriptors.cc @@ -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()}; diff --git a/src/interface-descriptors.h b/src/interface-descriptors.h index 9bde89ba0c..557b89388f 100644 --- a/src/interface-descriptors.h +++ b/src/interface-descriptors.h @@ -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) diff --git a/src/interpreter/interpreter-generator.cc b/src/interpreter/interpreter-generator.cc index 616c5ab85b..f8c846c39f 100644 --- a/src/interpreter/interpreter-generator.cc +++ b/src/interpreter/interpreter-generator.cc @@ -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 // // Concatenates the string values in registers to // + 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); diff --git a/src/mips/interface-descriptors-mips.cc b/src/mips/interface-descriptors-mips.cc index c1e8229e22..4eea1b38ab 100644 --- a/src/mips/interface-descriptors-mips.cc +++ b/src/mips/interface-descriptors-mips.cc @@ -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; } diff --git a/src/mips64/interface-descriptors-mips64.cc b/src/mips64/interface-descriptors-mips64.cc index 73889d2d34..787f7ba1a8 100644 --- a/src/mips64/interface-descriptors-mips64.cc +++ b/src/mips64/interface-descriptors-mips64.cc @@ -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; } diff --git a/src/ppc/interface-descriptors-ppc.cc b/src/ppc/interface-descriptors-ppc.cc index cba9275d90..82a2cb3acf 100644 --- a/src/ppc/interface-descriptors-ppc.cc +++ b/src/ppc/interface-descriptors-ppc.cc @@ -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; } diff --git a/src/runtime/runtime-interpreter.cc b/src/runtime/runtime-interpreter.cc index e6566cce48..815e6d0fec 100644 --- a/src/runtime/runtime-interpreter.cc +++ b/src/runtime/runtime-interpreter.cc @@ -35,23 +35,6 @@ RUNTIME_FUNCTION(Runtime_InterpreterNewClosure) { static_cast(pretenured_flag)); } -RUNTIME_FUNCTION(Runtime_InterpreterStringConcat) { - HandleScope scope(isolate); - DCHECK_LE(2, args.length()); - int const argc = args.length(); - ScopedVector> argv(argc); - - isolate->counters()->string_add_runtime()->Increment(); - IncrementalStringBuilder builder(isolate); - for (int i = 0; i < argc; ++i) { - Handle str = Handle::cast(args.at(i)); - if (str->length() != 0) { - builder.AppendString(str); - } - } - RETURN_RESULT_OR_FAILURE(isolate, builder.Finish()); -} - #ifdef V8_TRACE_IGNITION namespace { diff --git a/src/runtime/runtime-strings.cc b/src/runtime/runtime-strings.cc index b50093fed5..ecaf074ae6 100644 --- a/src/runtime/runtime-strings.cc +++ b/src/runtime/runtime-strings.cc @@ -108,7 +108,6 @@ MaybeHandle 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> argv(argc); + + isolate->counters()->string_add_runtime()->Increment(); + IncrementalStringBuilder builder(isolate); + for (int i = 0; i < argc; ++i) { + Handle str = Handle::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()); diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index ab22806e7b..6c6dd1d0cb 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -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) \ diff --git a/src/s390/interface-descriptors-s390.cc b/src/s390/interface-descriptors-s390.cc index 0028b9578b..028870a578 100644 --- a/src/s390/interface-descriptors-s390.cc +++ b/src/s390/interface-descriptors-s390.cc @@ -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; } diff --git a/src/v8.gyp b/src/v8.gyp index d81e7ee1fb..c07513badf 100644 --- a/src/v8.gyp +++ b/src/v8.gyp @@ -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', diff --git a/src/x64/interface-descriptors-x64.cc b/src/x64/interface-descriptors-x64.cc index b6ab7ca1af..e20f062608 100644 --- a/src/x64/interface-descriptors-x64.cc +++ b/src/x64/interface-descriptors-x64.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; } diff --git a/src/x87/interface-descriptors-x87.cc b/src/x87/interface-descriptors-x87.cc index 25707a34aa..b849eb9937 100644 --- a/src/x87/interface-descriptors-x87.cc +++ b/src/x87/interface-descriptors-x87.cc @@ -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; } diff --git a/test/cctest/compiler/test-run-bytecode-graph-builder.cc b/test/cctest/compiler/test-run-bytecode-graph-builder.cc index 6393bdb7fa..8a24656d78 100644 --- a/test/cctest/compiler/test-run-bytecode-graph-builder.cc +++ b/test/cctest/compiler/test-run-bytecode-graph-builder.cc @@ -44,7 +44,6 @@ static MaybeHandle CallFunction(Isolate* isolate, isolate->factory()->undefined_value(), 0, nullptr); } - template static MaybeHandle CallFunction(Isolate* isolate, Handle function, @@ -55,7 +54,6 @@ static MaybeHandle CallFunction(Isolate* isolate, argv); } - template class BytecodeGraphCallable { public: @@ -72,7 +70,6 @@ class BytecodeGraphCallable { Handle 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 > 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(isolate); + Factory* factory = isolate->factory(); + + uc16 array1[] = {2001, 2002, 2003}; + Vector two_byte_str_1(array1); + + uc16 array2[] = {1001, 1002, 1003, 1004, 1005, 1006}; + Vector two_byte_str_2(array2); + + uc16 array3[] = {1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009}; + Vector two_byte_str_3(array3); + + OneByteResource* external_resource = + new OneByteResource(StrDup("external"), 8); + v8::Local ext_string_local = + v8::String::NewExternalOneByte(ext_isolate, external_resource) + .ToLocalChecked(); + Handle external_string = v8::Utils::OpenHandle(*ext_string_local); + + Handle thin_string = + factory->NewStringFromAsciiChecked("thin_string"); + factory->InternalizeString(thin_string); + DCHECK_IMPLIES(FLAG_thin_strings, thin_string->IsThinString()); + + Handle 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 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 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>(); + Handle return_value = + callable(inputs[i], inputs[j]).ToHandleChecked(); + + Handle 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 lhs_value, Handle rhs_value) { switch (opcode) { @@ -1386,7 +1459,6 @@ bool get_compare_result(Token::Value opcode, Handle 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(); diff --git a/test/unittests/compiler/js-operator-unittest.cc b/test/unittests/compiler/js-operator-unittest.cc index 886fbe02ce..ad0553313f 100644 --- a/test/unittests/compiler/js-operator-unittest.cc +++ b/test/unittests/compiler/js-operator-unittest.cc @@ -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), diff --git a/test/unittests/compiler/js-typed-lowering-unittest.cc b/test/unittests/compiler/js-typed-lowering-unittest.cc index 925d1ff7be..8a1efe0801 100644 --- a/test/unittests/compiler/js-typed-lowering-unittest.cc +++ b/test/unittests/compiler/js-typed-lowering-unittest.cc @@ -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();