| Index: src/builtins/builtins-string.cc
|
| diff --git a/src/builtins/builtins-string.cc b/src/builtins/builtins-string.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7faf22da2f07e22ad9ca2677020d39150d8fa9f4
|
| --- /dev/null
|
| +++ b/src/builtins/builtins-string.cc
|
| @@ -0,0 +1,529 @@
|
| +// Copyright 2016 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.
|
| +
|
| +#include "src/builtins/builtins.h"
|
| +#include "src/builtins/builtins-utils.h"
|
| +
|
| +#include "src/code-factory.h"
|
| +
|
| +namespace v8 {
|
| +namespace internal {
|
| +
|
| +// -----------------------------------------------------------------------------
|
| +// ES6 section 21.1 String Objects
|
| +
|
| +// ES6 section 21.1.2.1 String.fromCharCode ( ...codeUnits )
|
| +void Builtins::Generate_StringFromCharCode(CodeStubAssembler* assembler) {
|
| + typedef CodeStubAssembler::Label Label;
|
| + typedef compiler::Node Node;
|
| + typedef CodeStubAssembler::Variable Variable;
|
| +
|
| + Node* code = assembler->Parameter(1);
|
| + Node* context = assembler->Parameter(4);
|
| +
|
| + // Check if we have exactly one argument (plus the implicit receiver), i.e.
|
| + // if the parent frame is not an arguments adaptor frame.
|
| + Label if_oneargument(assembler), if_notoneargument(assembler);
|
| + Node* parent_frame_pointer = assembler->LoadParentFramePointer();
|
| + Node* parent_frame_type =
|
| + assembler->Load(MachineType::Pointer(), parent_frame_pointer,
|
| + assembler->IntPtrConstant(
|
| + CommonFrameConstants::kContextOrFrameTypeOffset));
|
| + assembler->Branch(
|
| + assembler->WordEqual(
|
| + parent_frame_type,
|
| + assembler->SmiConstant(Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR))),
|
| + &if_notoneargument, &if_oneargument);
|
| +
|
| + assembler->Bind(&if_oneargument);
|
| + {
|
| + // Single argument case, perform fast single character string cache lookup
|
| + // for one-byte code units, or fall back to creating a single character
|
| + // string on the fly otherwise.
|
| + Node* code32 = assembler->TruncateTaggedToWord32(context, code);
|
| + Node* code16 = assembler->Word32And(
|
| + code32, assembler->Int32Constant(String::kMaxUtf16CodeUnit));
|
| + Node* result = assembler->StringFromCharCode(code16);
|
| + assembler->Return(result);
|
| + }
|
| +
|
| + assembler->Bind(&if_notoneargument);
|
| + {
|
| + // Determine the resulting string length.
|
| + Node* parent_frame_length =
|
| + assembler->Load(MachineType::Pointer(), parent_frame_pointer,
|
| + assembler->IntPtrConstant(
|
| + ArgumentsAdaptorFrameConstants::kLengthOffset));
|
| + Node* length = assembler->SmiToWord(parent_frame_length);
|
| +
|
| + // Assume that the resulting string contains only one-byte characters.
|
| + Node* result = assembler->AllocateSeqOneByteString(context, length);
|
| +
|
| + // Truncate all input parameters and append them to the resulting string.
|
| + Variable var_offset(assembler, MachineType::PointerRepresentation());
|
| + Label loop(assembler, &var_offset), done_loop(assembler);
|
| + var_offset.Bind(assembler->IntPtrConstant(0));
|
| + assembler->Goto(&loop);
|
| + assembler->Bind(&loop);
|
| + {
|
| + // Load the current {offset}.
|
| + Node* offset = var_offset.value();
|
| +
|
| + // Check if we're done with the string.
|
| + assembler->GotoIf(assembler->WordEqual(offset, length), &done_loop);
|
| +
|
| + // Load the next code point and truncate it to a 16-bit value.
|
| + Node* code = assembler->Load(
|
| + MachineType::AnyTagged(), parent_frame_pointer,
|
| + assembler->IntPtrAdd(
|
| + assembler->WordShl(assembler->IntPtrSub(length, offset),
|
| + assembler->IntPtrConstant(kPointerSizeLog2)),
|
| + assembler->IntPtrConstant(
|
| + CommonFrameConstants::kFixedFrameSizeAboveFp -
|
| + kPointerSize)));
|
| + Node* code32 = assembler->TruncateTaggedToWord32(context, code);
|
| + Node* code16 = assembler->Word32And(
|
| + code32, assembler->Int32Constant(String::kMaxUtf16CodeUnit));
|
| +
|
| + // Check if {code16} fits into a one-byte string.
|
| + Label if_codeisonebyte(assembler), if_codeistwobyte(assembler);
|
| + assembler->Branch(
|
| + assembler->Int32LessThanOrEqual(
|
| + code16, assembler->Int32Constant(String::kMaxOneByteCharCode)),
|
| + &if_codeisonebyte, &if_codeistwobyte);
|
| +
|
| + assembler->Bind(&if_codeisonebyte);
|
| + {
|
| + // The {code16} fits into the SeqOneByteString {result}.
|
| + assembler->StoreNoWriteBarrier(
|
| + MachineRepresentation::kWord8, result,
|
| + assembler->IntPtrAdd(
|
| + assembler->IntPtrConstant(SeqOneByteString::kHeaderSize -
|
| + kHeapObjectTag),
|
| + offset),
|
| + code16);
|
| + var_offset.Bind(
|
| + assembler->IntPtrAdd(offset, assembler->IntPtrConstant(1)));
|
| + assembler->Goto(&loop);
|
| + }
|
| +
|
| + assembler->Bind(&if_codeistwobyte);
|
| + {
|
| + // Allocate a SeqTwoByteString to hold the resulting string.
|
| + Node* cresult = assembler->AllocateSeqTwoByteString(context, length);
|
| +
|
| + // Copy all characters that were previously written to the
|
| + // SeqOneByteString in {result} over to the new {cresult}.
|
| + Variable var_coffset(assembler, MachineType::PointerRepresentation());
|
| + Label cloop(assembler, &var_coffset), done_cloop(assembler);
|
| + var_coffset.Bind(assembler->IntPtrConstant(0));
|
| + assembler->Goto(&cloop);
|
| + assembler->Bind(&cloop);
|
| + {
|
| + Node* coffset = var_coffset.value();
|
| + assembler->GotoIf(assembler->WordEqual(coffset, offset), &done_cloop);
|
| + Node* ccode = assembler->Load(
|
| + MachineType::Uint8(), result,
|
| + assembler->IntPtrAdd(
|
| + assembler->IntPtrConstant(SeqOneByteString::kHeaderSize -
|
| + kHeapObjectTag),
|
| + coffset));
|
| + assembler->StoreNoWriteBarrier(
|
| + MachineRepresentation::kWord16, cresult,
|
| + assembler->IntPtrAdd(
|
| + assembler->IntPtrConstant(SeqTwoByteString::kHeaderSize -
|
| + kHeapObjectTag),
|
| + assembler->WordShl(coffset, 1)),
|
| + ccode);
|
| + var_coffset.Bind(
|
| + assembler->IntPtrAdd(coffset, assembler->IntPtrConstant(1)));
|
| + assembler->Goto(&cloop);
|
| + }
|
| +
|
| + // Write the pending {code16} to {offset}.
|
| + assembler->Bind(&done_cloop);
|
| + assembler->StoreNoWriteBarrier(
|
| + MachineRepresentation::kWord16, cresult,
|
| + assembler->IntPtrAdd(
|
| + assembler->IntPtrConstant(SeqTwoByteString::kHeaderSize -
|
| + kHeapObjectTag),
|
| + assembler->WordShl(offset, 1)),
|
| + code16);
|
| +
|
| + // Copy the remaining parameters to the SeqTwoByteString {cresult}.
|
| + Label floop(assembler, &var_offset), done_floop(assembler);
|
| + assembler->Goto(&floop);
|
| + assembler->Bind(&floop);
|
| + {
|
| + // Compute the next {offset}.
|
| + Node* offset = assembler->IntPtrAdd(var_offset.value(),
|
| + assembler->IntPtrConstant(1));
|
| +
|
| + // Check if we're done with the string.
|
| + assembler->GotoIf(assembler->WordEqual(offset, length), &done_floop);
|
| +
|
| + // Load the next code point and truncate it to a 16-bit value.
|
| + Node* code = assembler->Load(
|
| + MachineType::AnyTagged(), parent_frame_pointer,
|
| + assembler->IntPtrAdd(
|
| + assembler->WordShl(
|
| + assembler->IntPtrSub(length, offset),
|
| + assembler->IntPtrConstant(kPointerSizeLog2)),
|
| + assembler->IntPtrConstant(
|
| + CommonFrameConstants::kFixedFrameSizeAboveFp -
|
| + kPointerSize)));
|
| + Node* code32 = assembler->TruncateTaggedToWord32(context, code);
|
| + Node* code16 = assembler->Word32And(
|
| + code32, assembler->Int32Constant(String::kMaxUtf16CodeUnit));
|
| +
|
| + // Store the truncated {code} point at the next offset.
|
| + assembler->StoreNoWriteBarrier(
|
| + MachineRepresentation::kWord16, cresult,
|
| + assembler->IntPtrAdd(
|
| + assembler->IntPtrConstant(SeqTwoByteString::kHeaderSize -
|
| + kHeapObjectTag),
|
| + assembler->WordShl(offset, 1)),
|
| + code16);
|
| + var_offset.Bind(offset);
|
| + assembler->Goto(&floop);
|
| + }
|
| +
|
| + // Return the SeqTwoByteString.
|
| + assembler->Bind(&done_floop);
|
| + assembler->Return(cresult);
|
| + }
|
| + }
|
| +
|
| + assembler->Bind(&done_loop);
|
| + assembler->Return(result);
|
| + }
|
| +}
|
| +
|
| +namespace { // for String.fromCodePoint
|
| +
|
| +bool IsValidCodePoint(Isolate* isolate, Handle<Object> value) {
|
| + if (!value->IsNumber() && !Object::ToNumber(value).ToHandle(&value)) {
|
| + return false;
|
| + }
|
| +
|
| + if (Object::ToInteger(isolate, value).ToHandleChecked()->Number() !=
|
| + value->Number()) {
|
| + return false;
|
| + }
|
| +
|
| + if (value->Number() < 0 || value->Number() > 0x10FFFF) {
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +uc32 NextCodePoint(Isolate* isolate, BuiltinArguments args, int index) {
|
| + Handle<Object> value = args.at<Object>(1 + index);
|
| + ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, value, Object::ToNumber(value), -1);
|
| + if (!IsValidCodePoint(isolate, value)) {
|
| + isolate->Throw(*isolate->factory()->NewRangeError(
|
| + MessageTemplate::kInvalidCodePoint, value));
|
| + return -1;
|
| + }
|
| + return DoubleToUint32(value->Number());
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +// ES6 section 21.1.2.2 String.fromCodePoint ( ...codePoints )
|
| +BUILTIN(StringFromCodePoint) {
|
| + HandleScope scope(isolate);
|
| + int const length = args.length() - 1;
|
| + if (length == 0) return isolate->heap()->empty_string();
|
| + DCHECK_LT(0, length);
|
| +
|
| + // Optimistically assume that the resulting String contains only one byte
|
| + // characters.
|
| + List<uint8_t> one_byte_buffer(length);
|
| + uc32 code = 0;
|
| + int index;
|
| + for (index = 0; index < length; index++) {
|
| + code = NextCodePoint(isolate, args, index);
|
| + if (code < 0) {
|
| + return isolate->heap()->exception();
|
| + }
|
| + if (code > String::kMaxOneByteCharCode) {
|
| + break;
|
| + }
|
| + one_byte_buffer.Add(code);
|
| + }
|
| +
|
| + if (index == length) {
|
| + RETURN_RESULT_OR_FAILURE(isolate, isolate->factory()->NewStringFromOneByte(
|
| + one_byte_buffer.ToConstVector()));
|
| + }
|
| +
|
| + List<uc16> two_byte_buffer(length - index);
|
| +
|
| + while (true) {
|
| + if (code <= unibrow::Utf16::kMaxNonSurrogateCharCode) {
|
| + two_byte_buffer.Add(code);
|
| + } else {
|
| + two_byte_buffer.Add(unibrow::Utf16::LeadSurrogate(code));
|
| + two_byte_buffer.Add(unibrow::Utf16::TrailSurrogate(code));
|
| + }
|
| +
|
| + if (++index == length) {
|
| + break;
|
| + }
|
| + code = NextCodePoint(isolate, args, index);
|
| + if (code < 0) {
|
| + return isolate->heap()->exception();
|
| + }
|
| + }
|
| +
|
| + Handle<SeqTwoByteString> result;
|
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
| + isolate, result,
|
| + isolate->factory()->NewRawTwoByteString(one_byte_buffer.length() +
|
| + two_byte_buffer.length()));
|
| +
|
| + CopyChars(result->GetChars(), one_byte_buffer.ToConstVector().start(),
|
| + one_byte_buffer.length());
|
| + CopyChars(result->GetChars() + one_byte_buffer.length(),
|
| + two_byte_buffer.ToConstVector().start(), two_byte_buffer.length());
|
| +
|
| + return *result;
|
| +}
|
| +
|
| +// ES6 section 21.1.3.1 String.prototype.charAt ( pos )
|
| +void Builtins::Generate_StringPrototypeCharAt(CodeStubAssembler* assembler) {
|
| + typedef CodeStubAssembler::Label Label;
|
| + typedef compiler::Node Node;
|
| + typedef CodeStubAssembler::Variable Variable;
|
| +
|
| + Node* receiver = assembler->Parameter(0);
|
| + Node* position = assembler->Parameter(1);
|
| + Node* context = assembler->Parameter(4);
|
| +
|
| + // Check that {receiver} is coercible to Object and convert it to a String.
|
| + receiver =
|
| + assembler->ToThisString(context, receiver, "String.prototype.charAt");
|
| +
|
| + // Convert the {position} to a Smi and check that it's in bounds of the
|
| + // {receiver}.
|
| + // TODO(bmeurer): Find an abstraction for this!
|
| + {
|
| + // Check if the {position} is already a Smi.
|
| + Variable var_position(assembler, MachineRepresentation::kTagged);
|
| + var_position.Bind(position);
|
| + Label if_positionissmi(assembler),
|
| + if_positionisnotsmi(assembler, Label::kDeferred);
|
| + assembler->Branch(assembler->WordIsSmi(position), &if_positionissmi,
|
| + &if_positionisnotsmi);
|
| + assembler->Bind(&if_positionisnotsmi);
|
| + {
|
| + // Convert the {position} to an Integer via the ToIntegerStub.
|
| + Callable callable = CodeFactory::ToInteger(assembler->isolate());
|
| + Node* index = assembler->CallStub(callable, context, position);
|
| +
|
| + // Check if the resulting {index} is now a Smi.
|
| + Label if_indexissmi(assembler, Label::kDeferred),
|
| + if_indexisnotsmi(assembler, Label::kDeferred);
|
| + assembler->Branch(assembler->WordIsSmi(index), &if_indexissmi,
|
| + &if_indexisnotsmi);
|
| +
|
| + assembler->Bind(&if_indexissmi);
|
| + {
|
| + var_position.Bind(index);
|
| + assembler->Goto(&if_positionissmi);
|
| + }
|
| +
|
| + assembler->Bind(&if_indexisnotsmi);
|
| + {
|
| + // The ToIntegerStub canonicalizes everything in Smi range to Smi
|
| + // representation, so any HeapNumber returned is not in Smi range.
|
| + // The only exception here is -0.0, which we treat as 0.
|
| + Node* index_value = assembler->LoadHeapNumberValue(index);
|
| + Label if_indexiszero(assembler, Label::kDeferred),
|
| + if_indexisnotzero(assembler, Label::kDeferred);
|
| + assembler->Branch(assembler->Float64Equal(
|
| + index_value, assembler->Float64Constant(0.0)),
|
| + &if_indexiszero, &if_indexisnotzero);
|
| +
|
| + assembler->Bind(&if_indexiszero);
|
| + {
|
| + var_position.Bind(assembler->SmiConstant(Smi::FromInt(0)));
|
| + assembler->Goto(&if_positionissmi);
|
| + }
|
| +
|
| + assembler->Bind(&if_indexisnotzero);
|
| + {
|
| + // The {index} is some other integral Number, that is definitely
|
| + // neither -0.0 nor in Smi range.
|
| + assembler->Return(assembler->EmptyStringConstant());
|
| + }
|
| + }
|
| + }
|
| + assembler->Bind(&if_positionissmi);
|
| + position = var_position.value();
|
| +
|
| + // Determine the actual length of the {receiver} String.
|
| + Node* receiver_length =
|
| + assembler->LoadObjectField(receiver, String::kLengthOffset);
|
| +
|
| + // Return "" if the Smi {position} is outside the bounds of the {receiver}.
|
| + Label if_positioninbounds(assembler),
|
| + if_positionnotinbounds(assembler, Label::kDeferred);
|
| + assembler->Branch(assembler->SmiAboveOrEqual(position, receiver_length),
|
| + &if_positionnotinbounds, &if_positioninbounds);
|
| + assembler->Bind(&if_positionnotinbounds);
|
| + assembler->Return(assembler->EmptyStringConstant());
|
| + assembler->Bind(&if_positioninbounds);
|
| + }
|
| +
|
| + // Load the character code at the {position} from the {receiver}.
|
| + Node* code = assembler->StringCharCodeAt(receiver, position);
|
| +
|
| + // And return the single character string with only that {code}.
|
| + Node* result = assembler->StringFromCharCode(code);
|
| + assembler->Return(result);
|
| +}
|
| +
|
| +// ES6 section 21.1.3.2 String.prototype.charCodeAt ( pos )
|
| +void Builtins::Generate_StringPrototypeCharCodeAt(
|
| + CodeStubAssembler* assembler) {
|
| + typedef CodeStubAssembler::Label Label;
|
| + typedef compiler::Node Node;
|
| + typedef CodeStubAssembler::Variable Variable;
|
| +
|
| + Node* receiver = assembler->Parameter(0);
|
| + Node* position = assembler->Parameter(1);
|
| + Node* context = assembler->Parameter(4);
|
| +
|
| + // Check that {receiver} is coercible to Object and convert it to a String.
|
| + receiver =
|
| + assembler->ToThisString(context, receiver, "String.prototype.charCodeAt");
|
| +
|
| + // Convert the {position} to a Smi and check that it's in bounds of the
|
| + // {receiver}.
|
| + // TODO(bmeurer): Find an abstraction for this!
|
| + {
|
| + // Check if the {position} is already a Smi.
|
| + Variable var_position(assembler, MachineRepresentation::kTagged);
|
| + var_position.Bind(position);
|
| + Label if_positionissmi(assembler),
|
| + if_positionisnotsmi(assembler, Label::kDeferred);
|
| + assembler->Branch(assembler->WordIsSmi(position), &if_positionissmi,
|
| + &if_positionisnotsmi);
|
| + assembler->Bind(&if_positionisnotsmi);
|
| + {
|
| + // Convert the {position} to an Integer via the ToIntegerStub.
|
| + Callable callable = CodeFactory::ToInteger(assembler->isolate());
|
| + Node* index = assembler->CallStub(callable, context, position);
|
| +
|
| + // Check if the resulting {index} is now a Smi.
|
| + Label if_indexissmi(assembler, Label::kDeferred),
|
| + if_indexisnotsmi(assembler, Label::kDeferred);
|
| + assembler->Branch(assembler->WordIsSmi(index), &if_indexissmi,
|
| + &if_indexisnotsmi);
|
| +
|
| + assembler->Bind(&if_indexissmi);
|
| + {
|
| + var_position.Bind(index);
|
| + assembler->Goto(&if_positionissmi);
|
| + }
|
| +
|
| + assembler->Bind(&if_indexisnotsmi);
|
| + {
|
| + // The ToIntegerStub canonicalizes everything in Smi range to Smi
|
| + // representation, so any HeapNumber returned is not in Smi range.
|
| + // The only exception here is -0.0, which we treat as 0.
|
| + Node* index_value = assembler->LoadHeapNumberValue(index);
|
| + Label if_indexiszero(assembler, Label::kDeferred),
|
| + if_indexisnotzero(assembler, Label::kDeferred);
|
| + assembler->Branch(assembler->Float64Equal(
|
| + index_value, assembler->Float64Constant(0.0)),
|
| + &if_indexiszero, &if_indexisnotzero);
|
| +
|
| + assembler->Bind(&if_indexiszero);
|
| + {
|
| + var_position.Bind(assembler->SmiConstant(Smi::FromInt(0)));
|
| + assembler->Goto(&if_positionissmi);
|
| + }
|
| +
|
| + assembler->Bind(&if_indexisnotzero);
|
| + {
|
| + // The {index} is some other integral Number, that is definitely
|
| + // neither -0.0 nor in Smi range.
|
| + assembler->Return(assembler->NaNConstant());
|
| + }
|
| + }
|
| + }
|
| + assembler->Bind(&if_positionissmi);
|
| + position = var_position.value();
|
| +
|
| + // Determine the actual length of the {receiver} String.
|
| + Node* receiver_length =
|
| + assembler->LoadObjectField(receiver, String::kLengthOffset);
|
| +
|
| + // Return NaN if the Smi {position} is outside the bounds of the {receiver}.
|
| + Label if_positioninbounds(assembler),
|
| + if_positionnotinbounds(assembler, Label::kDeferred);
|
| + assembler->Branch(assembler->SmiAboveOrEqual(position, receiver_length),
|
| + &if_positionnotinbounds, &if_positioninbounds);
|
| + assembler->Bind(&if_positionnotinbounds);
|
| + assembler->Return(assembler->NaNConstant());
|
| + assembler->Bind(&if_positioninbounds);
|
| + }
|
| +
|
| + // Load the character at the {position} from the {receiver}.
|
| + Node* value = assembler->StringCharCodeAt(receiver, position);
|
| + Node* result = assembler->SmiFromWord32(value);
|
| + assembler->Return(result);
|
| +}
|
| +
|
| +// ES6 section 21.1.3.25 String.prototype.toString ()
|
| +void Builtins::Generate_StringPrototypeToString(CodeStubAssembler* assembler) {
|
| + typedef compiler::Node Node;
|
| +
|
| + Node* receiver = assembler->Parameter(0);
|
| + Node* context = assembler->Parameter(3);
|
| +
|
| + Node* result = assembler->ToThisValue(
|
| + context, receiver, PrimitiveType::kString, "String.prototype.toString");
|
| + assembler->Return(result);
|
| +}
|
| +
|
| +// ES6 section 21.1.3.27 String.prototype.trim ()
|
| +BUILTIN(StringPrototypeTrim) {
|
| + HandleScope scope(isolate);
|
| + TO_THIS_STRING(string, "String.prototype.trim");
|
| + return *String::Trim(string, String::kTrim);
|
| +}
|
| +
|
| +// Non-standard WebKit extension
|
| +BUILTIN(StringPrototypeTrimLeft) {
|
| + HandleScope scope(isolate);
|
| + TO_THIS_STRING(string, "String.prototype.trimLeft");
|
| + return *String::Trim(string, String::kTrimLeft);
|
| +}
|
| +
|
| +// Non-standard WebKit extension
|
| +BUILTIN(StringPrototypeTrimRight) {
|
| + HandleScope scope(isolate);
|
| + TO_THIS_STRING(string, "String.prototype.trimRight");
|
| + return *String::Trim(string, String::kTrimRight);
|
| +}
|
| +
|
| +// ES6 section 21.1.3.28 String.prototype.valueOf ( )
|
| +void Builtins::Generate_StringPrototypeValueOf(CodeStubAssembler* assembler) {
|
| + typedef compiler::Node Node;
|
| +
|
| + Node* receiver = assembler->Parameter(0);
|
| + Node* context = assembler->Parameter(3);
|
| +
|
| + Node* result = assembler->ToThisValue(
|
| + context, receiver, PrimitiveType::kString, "String.prototype.valueOf");
|
| + assembler->Return(result);
|
| +}
|
| +
|
| +} // namespace internal
|
| +} // namespace v8
|
|
|