// 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"
#include "src/regexp/regexp-utils.h"
namespace v8 {
namespace internal {
typedef CodeStubAssembler::ResultMode ResultMode;
typedef CodeStubAssembler::RelationalComparisonMode RelationalComparisonMode;
namespace {
void GenerateStringEqual(CodeStubAssembler* assembler, ResultMode mode) {
// Here's pseudo-code for the algorithm below in case of kDontNegateResult
// mode; for kNegateResult mode we properly negate the result.
//
// if (lhs == rhs) return true;
// if (lhs->length() != rhs->length()) return false;
// if (lhs->IsInternalizedString() && rhs->IsInternalizedString()) {
// return false;
// }
// if (lhs->IsSeqOneByteString() && rhs->IsSeqOneByteString()) {
// for (i = 0; i != lhs->length(); ++i) {
// if (lhs[i] != rhs[i]) return false;
// }
// return true;
// }
// return %StringEqual(lhs, rhs);
typedef CodeStubAssembler::Label Label;
typedef compiler::Node Node;
typedef CodeStubAssembler::Variable Variable;
Node* lhs = assembler->Parameter(0);
Node* rhs = assembler->Parameter(1);
Node* context = assembler->Parameter(2);
Label if_equal(assembler), if_notequal(assembler);
// Fast check to see if {lhs} and {rhs} refer to the same String object.
Label if_same(assembler), if_notsame(assembler);
assembler->Branch(assembler->WordEqual(lhs, rhs), &if_same, &if_notsame);
assembler->Bind(&if_same);
assembler->Goto(&if_equal);
assembler->Bind(&if_notsame);
{
// The {lhs} and {rhs} don't refer to the exact same String object.
// Load the length of {lhs} and {rhs}.
Node* lhs_length = assembler->LoadStringLength(lhs);
Node* rhs_length = assembler->LoadStringLength(rhs);
// Check if the lengths of {lhs} and {rhs} are equal.
Label if_lengthisequal(assembler), if_lengthisnotequal(assembler);
assembler->Branch(assembler->WordEqual(lhs_length, rhs_length),
&if_lengthisequal, &if_lengthisnotequal);
assembler->Bind(&if_lengthisequal);
{
// Load instance types of {lhs} and {rhs}.
Node* lhs_instance_type = assembler->LoadInstanceType(lhs);
Node* rhs_instance_type = assembler->LoadInstanceType(rhs);
// Combine the instance types into a single 16-bit value, so we can check
// both of them at once.
Node* both_instance_types = assembler->Word32Or(
lhs_instance_type,
assembler->Word32Shl(rhs_instance_type, assembler->Int32Constant(8)));
// Check if both {lhs} and {rhs} are internalized.
int const kBothInternalizedMask =
kIsNotInternalizedMask | (kIsNotInternalizedMask << 8);
int const kBothInternalizedTag =
kInternalizedTag | (kInternalizedTag << 8);
Label if_bothinternalized(assembler), if_notbothinternalized(assembler);
assembler->Branch(assembler->Word32Equal(
assembler->Word32And(both_instance_types,
assembler->Int32Constant(
kBothInternalizedMask)),
assembler->Int32Constant(kBothInternalizedTag)),
&if_bothinternalized, &if_notbothinternalized);
assembler->Bind(&if_bothinternalized);
{
// Fast negative check for internalized-to-internalized equality.
assembler->Goto(&if_notequal);
}
assembler->Bind(&if_notbothinternalized);
{
// Check that both {lhs} and {rhs} are flat one-byte strings.
int const kBothSeqOneByteStringMask =
kStringEncodingMask | kStringRepresentationMask |
((kStringEncodingMask | kStringRepresentationMask) << 8);
int const kBothSeqOneByteStringTag =
kOneByteStringTag | kSeqStringTag |
((kOneByteStringTag | kSeqStringTag) << 8);
Label if_bothonebyteseqstrings(assembler),
if_notbothonebyteseqstrings(assembler);
assembler->Branch(
assembler->Word32Equal(
assembler->Word32And(
both_instance_types,
assembler->Int32Constant(kBothSeqOneByteStringMask)),
assembler->Int32Constant(kBothSeqOneByteStringTag)),
&if_bothonebyteseqstrings, &if_notbothonebyteseqstrings);
assembler->Bind(&if_bothonebyteseqstrings);
{
// Compute the effective offset of the first character.
Node* begin = assembler->IntPtrConstant(
SeqOneByteString::kHeaderSize - kHeapObjectTag);
// Compute the first offset after the string from the length.
Node* end =
assembler->IntPtrAdd(begin, assembler->SmiUntag(lhs_length));
// Loop over the {lhs} and {rhs} strings to see if they are equal.
Variable var_offset(assembler, MachineType::PointerRepresentation());
Label loop(assembler, &var_offset);
var_offset.Bind(begin);
assembler->Goto(&loop);
assembler->Bind(&loop);
{
// Check if {offset} equals {end}.
Node* offset = var_offset.value();
Label if_done(assembler), if_notdone(assembler);
assembler->Branch(assembler->WordEqual(offset, end), &if_done,
&if_notdone);
assembler->Bind(&if_notdone);
{
// Load the next characters from {lhs} and {rhs}.
Node* lhs_value =
assembler->Load(MachineType::Uint8(), lhs, offset);
Node* rhs_value =
assembler->Load(MachineType::Uint8(), rhs, offset);
// Check if the characters match.
Label if_valueissame(assembler), if_valueisnotsame(assembler);
assembler->Branch(assembler->Word32Equal(lhs_value, rhs_value),
&if_valueissame, &if_valueisnotsame);
assembler->Bind(&if_valueissame);
{
// Advance to next character.
var_offset.Bind(
assembler->IntPtrAdd(offset, assembler->IntPtrConstant(1)));
}
assembler->Goto(&loop);
assembler->Bind(&if_valueisnotsame);
assembler->Goto(&if_notequal);
}
assembler->Bind(&if_done);
assembler->Goto(&if_equal);
}
}
assembler->Bind(&if_notbothonebyteseqstrings);
{
// TODO(bmeurer): Add fast case support for flattened cons strings;
// also add support for two byte string equality checks.
Runtime::FunctionId function_id =
(mode == ResultMode::kDontNegateResult)
? Runtime::kStringEqual
: Runtime::kStringNotEqual;
assembler->TailCallRuntime(function_id, context, lhs, rhs);
}
}
}
assembler->Bind(&if_lengthisnotequal);
{
// Mismatch in length of {lhs} and {rhs}, cannot be equal.
assembler->Goto(&if_notequal);
}
}
assembler->Bind(&if_equal);
assembler->Return(
assembler->BooleanConstant(mode == ResultMode::kDontNegateResult));
assembler->Bind(&if_notequal);
assembler->Return(
assembler->BooleanConstant(mode == ResultMode::kNegateResult));
}
void GenerateStringRelationalComparison(CodeStubAssembler* assembler,
RelationalComparisonMode mode) {
typedef CodeStubAssembler::Label Label;
typedef compiler::Node Node;
typedef CodeStubAssembler::Variable Variable;
Node* lhs = assembler->Parameter(0);
Node* rhs = assembler->Parameter(1);
Node* context = assembler->Parameter(2);
Label if_less(assembler), if_equal(assembler), if_greater(assembler);
// Fast check to see if {lhs} and {rhs} refer to the same String object.
Label if_same(assembler), if_notsame(assembler);
assembler->Branch(assembler->WordEqual(lhs, rhs), &if_same, &if_notsame);
assembler->Bind(&if_same);
assembler->Goto(&if_equal);
assembler->Bind(&if_notsame);
{
// Load instance types of {lhs} and {rhs}.
Node* lhs_instance_type = assembler->LoadInstanceType(lhs);
Node* rhs_instance_type = assembler->LoadInstanceType(rhs);
// Combine the instance types into a single 16-bit value, so we can check
// both of them at once.
Node* both_instance_types = assembler->Word32Or(
lhs_instance_type,
assembler->Word32Shl(rhs_instance_type, assembler->Int32Constant(8)));
// Check that both {lhs} and {rhs} are flat one-byte strings.
int const kBothSeqOneByteStringMask =
kStringEncodingMask | kStringRepresentationMask |
((kStringEncodingMask | kStringRepresentationMask) << 8);
int const kBothSeqOneByteStringTag =
kOneByteStringTag | kSeqStringTag |
((kOneByteStringTag | kSeqStringTag) << 8);
Label if_bothonebyteseqstrings(assembler),
if_notbothonebyteseqstrings(assembler);
assembler->Branch(assembler->Word32Equal(
assembler->Word32And(both_instance_types,
assembler->Int32Constant(
kBothSeqOneByteStringMask)),
assembler->Int32Constant(kBothSeqOneByteStringTag)),
&if_bothonebyteseqstrings, &if_notbothonebyteseqstrings);
assembler->Bind(&if_bothonebyteseqstrings);
{
// Load the length of {lhs} and {rhs}.
Node* lhs_length = assembler->LoadStringLength(lhs);
Node* rhs_length = assembler->LoadStringLength(rhs);
// Determine the minimum length.
Node* length = assembler->SmiMin(lhs_length, rhs_length);
// Compute the effective offset of the first character.
Node* begin = assembler->IntPtrConstant(SeqOneByteString::kHeaderSize -
kHeapObjectTag);
// Compute the first offset after the string from the length.
Node* end = assembler->IntPtrAdd(begin, assembler->SmiUntag(length));
// Loop over the {lhs} and {rhs} strings to see if they are equal.
Variable var_offset(assembler, MachineType::PointerRepresentation());
Label loop(assembler, &var_offset);
var_offset.Bind(begin);
assembler->Goto(&loop);
assembler->Bind(&loop);
{
// Check if {offset} equals {end}.
Node* offset = var_offset.value();
Label if_done(assembler), if_notdone(assembler);
assembler->Branch(assembler->WordEqual(offset, end), &if_done,
&if_notdone);
assembler->Bind(&if_notdone);
{
// Load the next characters from {lhs} and {rhs}.
Node* lhs_value = assembler->Load(MachineType::Uint8(), lhs, offset);
Node* rhs_value = assembler->Load(MachineType::Uint8(), rhs, offset);
// Check if the characters match.
Label if_valueissame(assembler), if_valueisnotsame(assembler);
assembler->Branch(assembler->Word32Equal(lhs_value, rhs_value),
&if_valueissame, &if_valueisnotsame);
assembler->Bind(&if_valueissame);
{
// Advance to next character.
var_offset.Bind(
assembler->IntPtrAdd(offset, assembler->IntPtrConstant(1)));
}
assembler->Goto(&loop);
assembler->Bind(&if_valueisnotsame);
assembler->Branch(assembler->Uint32LessThan(lhs_value, rhs_value),
&if_less, &if_greater);
}
assembler->Bind(&if_done);
{
// All characters up to the min length are equal, decide based on
// string length.
Label if_lengthisequal(assembler), if_lengthisnotequal(assembler);
assembler->Branch(assembler->SmiEqual(lhs_length, rhs_length),
&if_lengthisequal, &if_lengthisnotequal);
assembler->Bind(&if_lengthisequal);
assembler->Goto(&if_equal);
assembler->Bind(&if_lengthisnotequal);
assembler->BranchIfSmiLessThan(lhs_length, rhs_length, &if_less,
&if_greater);
}
}
}
assembler->Bind(&if_notbothonebyteseqstrings);
{
// TODO(bmeurer): Add fast case support for flattened cons strings;
// also add support for two byte string relational comparisons.
switch (mode) {
case RelationalComparisonMode::kLessThan:
assembler->TailCallRuntime(Runtime::kStringLessThan, context, lhs,
rhs);
break;
case RelationalComparisonMode::kLessThanOrEqual:
assembler->TailCallRuntime(Runtime::kStringLessThanOrEqual, context,
lhs, rhs);
break;
case RelationalComparisonMode::kGreaterThan:
assembler->TailCallRuntime(Runtime::kStringGreaterThan, context, lhs,
rhs);
break;
case RelationalComparisonMode::kGreaterThanOrEqual:
assembler->TailCallRuntime(Runtime::kStringGreaterThanOrEqual,
context, lhs, rhs);
break;
}
}
}
assembler->Bind(&if_less);
switch (mode) {
case RelationalComparisonMode::kLessThan:
case RelationalComparisonMode::kLessThanOrEqual:
assembler->Return(assembler->BooleanConstant(true));
break;
case RelationalComparisonMode::kGreaterThan:
case RelationalComparisonMode::kGreaterThanOrEqual:
assembler->Return(assembler->BooleanConstant(false));
break;
}
assembler->Bind(&if_equal);
switch (mode) {
case RelationalComparisonMode::kLessThan:
case RelationalComparisonMode::kGreaterThan:
assembler->Return(assembler->BooleanConstant(false));
break;
case RelationalComparisonMode::kLessThanOrEqual:
case RelationalComparisonMode::kGreaterThanOrEqual:
assembler->Return(assembler->BooleanConstant(true));
break;
}
assembler->Bind(&if_greater);
switch (mode) {
case RelationalComparisonMode::kLessThan:
case RelationalComparisonMode::kLessThanOrEqual:
assembler->Return(assembler->BooleanConstant(false));
break;
case RelationalComparisonMode::kGreaterThan:
case RelationalComparisonMode::kGreaterThanOrEqual:
assembler->Return(assembler->BooleanConstant(true));
break;
}
}
} // namespace
// static
void Builtins::Generate_StringEqual(CodeStubAssembler* assembler) {
GenerateStringEqual(assembler, ResultMode::kDontNegateResult);
}
// static
void Builtins::Generate_StringNotEqual(CodeStubAssembler* assembler) {
GenerateStringEqual(assembler, ResultMode::kNegateResult);
}
// static
void Builtins::Generate_StringLessThan(CodeStubAssembler* assembler) {
GenerateStringRelationalComparison(assembler,
RelationalComparisonMode::kLessThan);
}
// static
void Builtins::Generate_StringLessThanOrEqual(CodeStubAssembler* assembler) {
GenerateStringRelationalComparison(
assembler, RelationalComparisonMode::kLessThanOrEqual);
}
// static
void Builtins::Generate_StringGreaterThan(CodeStubAssembler* assembler) {
GenerateStringRelationalComparison(assembler,
RelationalComparisonMode::kGreaterThan);
}
// static
void Builtins::Generate_StringGreaterThanOrEqual(CodeStubAssembler* assembler) {
GenerateStringRelationalComparison(
assembler, RelationalComparisonMode::kGreaterThanOrEqual);
}
// -----------------------------------------------------------------------------
// 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* argc = assembler->ChangeInt32ToIntPtr(
assembler->Parameter(BuiltinDescriptor::kArgumentsCount));
Node* context = assembler->Parameter(BuiltinDescriptor::kContext);
CodeStubArguments arguments(assembler, argc);
// 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);
assembler->Branch(assembler->WordEqual(argc, assembler->IntPtrConstant(1)),
&if_oneargument, &if_notoneargument);
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* code = arguments.AtIndex(0);
Node* code32 = assembler->TruncateTaggedToWord32(context, code);
Node* code16 = assembler->Word32And(
code32, assembler->Int32Constant(String::kMaxUtf16CodeUnit));
Node* result = assembler->StringFromCharCode(code16);
arguments.PopAndReturn(result);
}
Node* code16 = nullptr;
assembler->Bind(&if_notoneargument);
{
Label two_byte(assembler);
// Assume that the resulting string contains only one-byte characters.
Node* one_byte_result = assembler->AllocateSeqOneByteString(context, argc);
Variable max_index(assembler, MachineType::PointerRepresentation());
max_index.Bind(assembler->IntPtrConstant(0));
// Iterate over the incoming arguments, converting them to 8-bit character
// codes. Stop if any of the conversions generates a code that doesn't fit
// in 8 bits.
CodeStubAssembler::VariableList vars({&max_index}, assembler->zone());
arguments.ForEach(vars, [context, &two_byte, &max_index, &code16,
one_byte_result](CodeStubAssembler* assembler,
Node* arg) {
Node* code32 = assembler->TruncateTaggedToWord32(context, arg);
code16 = assembler->Word32And(
code32, assembler->Int32Constant(String::kMaxUtf16CodeUnit));
assembler->GotoIf(
assembler->Int32GreaterThan(
code16, assembler->Int32Constant(String::kMaxOneByteCharCode)),
&two_byte);
// The {code16} fits into the SeqOneByteString {one_byte_result}.
Node* offset = assembler->ElementOffsetFromIndex(
max_index.value(), UINT8_ELEMENTS,
CodeStubAssembler::INTPTR_PARAMETERS,
SeqOneByteString::kHeaderSize - kHeapObjectTag);
assembler->StoreNoWriteBarrier(MachineRepresentation::kWord8,
one_byte_result, offset, code16);
max_index.Bind(assembler->IntPtrAdd(max_index.value(),
assembler->IntPtrConstant(1)));
});
arguments.PopAndReturn(one_byte_result);
assembler->Bind(&two_byte);
// At least one of the characters in the string requires a 16-bit
// representation. Allocate a SeqTwoByteString to hold the resulting
// string.
Node* two_byte_result = assembler->AllocateSeqTwoByteString(context, argc);
// Copy the characters that have already been put in the 8-bit string into
// their corresponding positions in the new 16-bit string.
Node* zero = assembler->IntPtrConstant(0);
assembler->CopyStringCharacters(
one_byte_result, two_byte_result, zero, zero, max_index.value(),
String::ONE_BYTE_ENCODING, String::TWO_BYTE_ENCODING,
CodeStubAssembler::INTPTR_PARAMETERS);
// Write the character that caused the 8-bit to 16-bit fault.
Node* max_index_offset = assembler->ElementOffsetFromIndex(
max_index.value(), UINT16_ELEMENTS,
CodeStubAssembler::INTPTR_PARAMETERS,
SeqTwoByteString::kHeaderSize - kHeapObjectTag);
assembler->StoreNoWriteBarrier(MachineRepresentation::kWord16,
two_byte_result, max_index_offset, code16);
max_index.Bind(
assembler->IntPtrAdd(max_index.value(), assembler->IntPtrConstant(1)));
// Resume copying the passed-in arguments from the same place where the
// 8-bit copy stopped, but this time copying over all of the characters
// using a 16-bit representation.
arguments.ForEach(
vars,
[context, two_byte_result, &max_index](CodeStubAssembler* assembler,
Node* arg) {
Node* code32 = assembler->TruncateTaggedToWord32(context, arg);
Node* code16 = assembler->Word32And(
code32, assembler->Int32Constant(String::kMaxUtf16CodeUnit));
Node* offset = assembler->ElementOffsetFromIndex(
max_index.value(), UINT16_ELEMENTS,
CodeStubAssembler::INTPTR_PARAMETERS,
SeqTwoByteString::kHeaderSize - kHeapObjectTag);
assembler->StoreNoWriteBarrier(MachineRepresentation::kWord16,
two_byte_result, offset, code16);
max_index.Bind(assembler->IntPtrAdd(max_index.value(),
assembler->IntPtrConstant(1)));
},
max_index.value());
arguments.PopAndReturn(two_byte_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 <= static_cast<uc32>(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;
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}.
{
Label return_emptystring(assembler, Label::kDeferred);
position = assembler->ToInteger(context, position,
CodeStubAssembler::kTruncateMinusZero);
assembler->GotoUnless(assembler->TaggedIsSmi(position),
&return_emptystring);
// 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);
assembler->Branch(assembler->SmiAboveOrEqual(position, receiver_length),
&return_emptystring, &if_positioninbounds);
assembler->Bind(&return_emptystring);
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;
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}.
{
Label return_nan(assembler, Label::kDeferred);
position = assembler->ToInteger(context, position,
CodeStubAssembler::kTruncateMinusZero);
assembler->GotoUnless(assembler->TaggedIsSmi(position), &return_nan);
// 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);
assembler->Branch(assembler->SmiAboveOrEqual(position, receiver_length),
&return_nan, &if_positioninbounds);
assembler->Bind(&return_nan);
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.6
// String.prototype.endsWith ( searchString [ , endPosition ] )
BUILTIN(StringPrototypeEndsWith) {
HandleScope handle_scope(isolate);
TO_THIS_STRING(str, "String.prototype.endsWith");
// Check if the search string is a regExp and fail if it is.
Handle<Object> search = args.atOrUndefined(isolate, 1);
Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search);
if (is_reg_exp.IsNothing()) {
DCHECK(isolate->has_pending_exception());
return isolate->heap()->exception();
}
if (is_reg_exp.FromJust()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kFirstArgumentNotRegExp,
isolate->factory()->NewStringFromStaticChars(
"String.prototype.endsWith")));
}
Handle<String> search_string;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, search_string,
Object::ToString(isolate, search));
Handle<Object> position = args.atOrUndefined(isolate, 2);
int end;
if (position->IsUndefined(isolate)) {
end = str->length();
} else {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, position,
Object::ToInteger(isolate, position));
double index = std::max(position->Number(), 0.0);
index = std::min(index, static_cast<double>(str->length()));
end = static_cast<uint32_t>(index);
}
int start = end - search_string->length();
if (start < 0) return isolate->heap()->false_value();
FlatStringReader str_reader(isolate, String::Flatten(str));
FlatStringReader search_reader(isolate, String::Flatten(search_string));
for (int i = 0; i < search_string->length(); i++) {
if (str_reader.Get(start + i) != search_reader.Get(i)) {
return isolate->heap()->false_value();
}
}
return isolate->heap()->true_value();
}
// ES6 section 21.1.3.7
// String.prototype.includes ( searchString [ , position ] )
BUILTIN(StringPrototypeIncludes) {
HandleScope handle_scope(isolate);
TO_THIS_STRING(str, "String.prototype.includes");
// Check if the search string is a regExp and fail if it is.
Handle<Object> search = args.atOrUndefined(isolate, 1);
Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search);
if (is_reg_exp.IsNothing()) {
DCHECK(isolate->has_pending_exception());
return isolate->heap()->exception();
}
if (is_reg_exp.FromJust()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kFirstArgumentNotRegExp,
isolate->factory()->NewStringFromStaticChars(
"String.prototype.includes")));
}
Handle<String> search_string;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, search_string,
Object::ToString(isolate, search));
Handle<Object> position;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, position,
Object::ToInteger(isolate, args.atOrUndefined(isolate, 2)));
double index = std::max(position->Number(), 0.0);
index = std::min(index, static_cast<double>(str->length()));
int index_in_str = String::IndexOf(isolate, str, search_string,
static_cast<uint32_t>(index));
return *isolate->factory()->ToBoolean(index_in_str != -1);
}
// ES6 section 21.1.3.8 String.prototype.indexOf ( searchString [ , position ] )
BUILTIN(StringPrototypeIndexOf) {
HandleScope handle_scope(isolate);
return String::IndexOf(isolate, args.receiver(),
args.atOrUndefined(isolate, 1),
args.atOrUndefined(isolate, 2));
}
// ES6 section 21.1.3.9
// String.prototype.lastIndexOf ( searchString [ , position ] )
BUILTIN(StringPrototypeLastIndexOf) {
HandleScope handle_scope(isolate);
return String::LastIndexOf(isolate, args.receiver(),
args.atOrUndefined(isolate, 1),
args.atOrUndefined(isolate, 2));
}
// ES6 section 21.1.3.10 String.prototype.localeCompare ( that )
//
// This function is implementation specific. For now, we do not
// do anything locale specific.
// If internationalization is enabled, then i18n.js will override this function
// and provide the proper functionality, so this is just a fallback.
BUILTIN(StringPrototypeLocaleCompare) {
HandleScope handle_scope(isolate);
DCHECK_EQ(2, args.length());
TO_THIS_STRING(str1, "String.prototype.localeCompare");
Handle<String> str2;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, str2, Object::ToString(isolate, args.at<Object>(1)));
if (str1.is_identical_to(str2)) return Smi::kZero; // Equal.
int str1_length = str1->length();
int str2_length = str2->length();
// Decide trivial cases without flattening.
if (str1_length == 0) {
if (str2_length == 0) return Smi::kZero; // Equal.
return Smi::FromInt(-str2_length);
} else {
if (str2_length == 0) return Smi::FromInt(str1_length);
}
int end = str1_length < str2_length ? str1_length : str2_length;
// No need to flatten if we are going to find the answer on the first
// character. At this point we know there is at least one character
// in each string, due to the trivial case handling above.
int d = str1->Get(0) - str2->Get(0);
if (d != 0) return Smi::FromInt(d);
str1 = String::Flatten(str1);
str2 = String::Flatten(str2);
DisallowHeapAllocation no_gc;
String::FlatContent flat1 = str1->GetFlatContent();
String::FlatContent flat2 = str2->GetFlatContent();
for (int i = 0; i < end; i++) {
if (flat1.Get(i) != flat2.Get(i)) {
return Smi::FromInt(flat1.Get(i) - flat2.Get(i));
}
}
return Smi::FromInt(str1_length - str2_length);
}
// ES6 section 21.1.3.12 String.prototype.normalize ( [form] )
//
// Simply checks the argument is valid and returns the string itself.
// If internationalization is enabled, then i18n.js will override this function
// and provide the proper functionality, so this is just a fallback.
BUILTIN(StringPrototypeNormalize) {
HandleScope handle_scope(isolate);
TO_THIS_STRING(string, "String.prototype.normalize");
Handle<Object> form_input = args.atOrUndefined(isolate, 1);
if (form_input->IsUndefined(isolate)) return *string;
Handle<String> form;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, form,
Object::ToString(isolate, form_input));
if (!(String::Equals(form,
isolate->factory()->NewStringFromStaticChars("NFC")) ||
String::Equals(form,
isolate->factory()->NewStringFromStaticChars("NFD")) ||
String::Equals(form,
isolate->factory()->NewStringFromStaticChars("NFKC")) ||
String::Equals(form,
isolate->factory()->NewStringFromStaticChars("NFKD")))) {
Handle<String> valid_forms =
isolate->factory()->NewStringFromStaticChars("NFC, NFD, NFKC, NFKD");
THROW_NEW_ERROR_RETURN_FAILURE(
isolate,
NewRangeError(MessageTemplate::kNormalizationForm, valid_forms));
}
return *string;
}
// ES6 section B.2.3.1 String.prototype.substr ( start, length )
void Builtins::Generate_StringPrototypeSubstr(CodeStubAssembler* a) {
typedef CodeStubAssembler::Label Label;
typedef compiler::Node Node;
typedef CodeStubAssembler::Variable Variable;
Label out(a), handle_length(a);
Variable var_start(a, MachineRepresentation::kTagged);
Variable var_length(a, MachineRepresentation::kTagged);
Node* const receiver = a->Parameter(0);
Node* const start = a->Parameter(1);
Node* const length = a->Parameter(2);
Node* const context = a->Parameter(5);
Node* const zero = a->SmiConstant(Smi::kZero);
// Check that {receiver} is coercible to Object and convert it to a String.
Node* const string =
a->ToThisString(context, receiver, "String.prototype.substr");
Node* const string_length = a->LoadStringLength(string);
// Conversions and bounds-checks for {start}.
{
Node* const start_int =
a->ToInteger(context, start, CodeStubAssembler::kTruncateMinusZero);
Label if_issmi(a), if_isheapnumber(a, Label::kDeferred);
a->Branch(a->TaggedIsSmi(start_int), &if_issmi, &if_isheapnumber);
a->Bind(&if_issmi);
{
Node* const length_plus_start = a->SmiAdd(string_length, start_int);
var_start.Bind(a->Select(a->SmiLessThan(start_int, zero),
a->SmiMax(length_plus_start, zero), start_int));
a->Goto(&handle_length);
}
a->Bind(&if_isheapnumber);
{
// If {start} is a heap number, it is definitely out of bounds. If it is
// negative, {start} = max({string_length} + {start}),0) = 0'. If it is
// positive, set {start} to {string_length} which ultimately results in
// returning an empty string.
Node* const float_zero = a->Float64Constant(0.);
Node* const start_float = a->LoadHeapNumberValue(start_int);
var_start.Bind(a->Select(a->Float64LessThan(start_float, float_zero),
zero, string_length));
a->Goto(&handle_length);
}
}
// Conversions and bounds-checks for {length}.
a->Bind(&handle_length);
{
Label if_issmi(a), if_isheapnumber(a, Label::kDeferred);
// Default to {string_length} if {length} is undefined.
{
Label if_isundefined(a, Label::kDeferred), if_isnotundefined(a);
a->Branch(a->WordEqual(length, a->UndefinedConstant()), &if_isundefined,
&if_isnotundefined);
a->Bind(&if_isundefined);
var_length.Bind(string_length);
a->Goto(&if_issmi);
a->Bind(&if_isnotundefined);
var_length.Bind(
a->ToInteger(context, length, CodeStubAssembler::kTruncateMinusZero));
}
a->Branch(a->TaggedIsSmi(var_length.value()), &if_issmi, &if_isheapnumber);
// Set {length} to min(max({length}, 0), {string_length} - {start}
a->Bind(&if_issmi);
{
Node* const positive_length = a->SmiMax(var_length.value(), zero);
Node* const minimal_length = a->SmiSub(string_length, var_start.value());
var_length.Bind(a->SmiMin(positive_length, minimal_length));
a->GotoUnless(a->SmiLessThanOrEqual(var_length.value(), zero), &out);
a->Return(a->EmptyStringConstant());
}
a->Bind(&if_isheapnumber);
{
// If {length} is a heap number, it is definitely out of bounds. There are
// two cases according to the spec: if it is negative, "" is returned; if
// it is positive, then length is set to {string_length} - {start}.
CSA_ASSERT(a, a->WordEqual(a->LoadMap(var_length.value()),
a->HeapNumberMapConstant()));
Label if_isnegative(a), if_ispositive(a);
Node* const float_zero = a->Float64Constant(0.);
Node* const length_float = a->LoadHeapNumberValue(var_length.value());
a->Branch(a->Float64LessThan(length_float, float_zero), &if_isnegative,
&if_ispositive);
a->Bind(&if_isnegative);
a->Return(a->EmptyStringConstant());
a->Bind(&if_ispositive);
{
var_length.Bind(a->SmiSub(string_length, var_start.value()));
a->GotoUnless(a->SmiLessThanOrEqual(var_length.value(), zero), &out);
a->Return(a->EmptyStringConstant());
}
}
}
a->Bind(&out);
{
Node* const end = a->SmiAdd(var_start.value(), var_length.value());
Node* const result = a->SubString(context, string, var_start.value(), end);
a->Return(result);
}
}
namespace {
compiler::Node* ToSmiBetweenZeroAnd(CodeStubAssembler* a,
compiler::Node* context,
compiler::Node* value,
compiler::Node* limit) {
typedef CodeStubAssembler::Label Label;
typedef compiler::Node Node;
typedef CodeStubAssembler::Variable Variable;
Label out(a);
Variable var_result(a, MachineRepresentation::kTagged);
Node* const value_int =
a->ToInteger(context, value, CodeStubAssembler::kTruncateMinusZero);
Label if_issmi(a), if_isnotsmi(a, Label::kDeferred);
a->Branch(a->TaggedIsSmi(value_int), &if_issmi, &if_isnotsmi);
a->Bind(&if_issmi);
{
Label if_isinbounds(a), if_isoutofbounds(a, Label::kDeferred);
a->Branch(a->SmiAbove(value_int, limit), &if_isoutofbounds, &if_isinbounds);
a->Bind(&if_isinbounds);
{
var_result.Bind(value_int);
a->Goto(&out);
}
a->Bind(&if_isoutofbounds);
{
Node* const zero = a->SmiConstant(Smi::kZero);
var_result.Bind(a->Select(a->SmiLessThan(value_int, zero), zero, limit));
a->Goto(&out);
}
}
a->Bind(&if_isnotsmi);
{
// {value} is a heap number - in this case, it is definitely out of bounds.
CSA_ASSERT(a,
a->WordEqual(a->LoadMap(value_int), a->HeapNumberMapConstant()));
Node* const float_zero = a->Float64Constant(0.);
Node* const smi_zero = a->SmiConstant(Smi::kZero);
Node* const value_float = a->LoadHeapNumberValue(value_int);
var_result.Bind(a->Select(a->Float64LessThan(value_float, float_zero),
smi_zero, limit));
a->Goto(&out);
}
a->Bind(&out);
return var_result.value();
}
} // namespace
// ES6 section 21.1.3.19 String.prototype.substring ( start, end )
void Builtins::Generate_StringPrototypeSubstring(CodeStubAssembler* a) {
typedef CodeStubAssembler::Label Label;
typedef compiler::Node Node;
typedef CodeStubAssembler::Variable Variable;
Label out(a);
Variable var_start(a, MachineRepresentation::kTagged);
Variable var_end(a, MachineRepresentation::kTagged);
Node* const receiver = a->Parameter(0);
Node* const start = a->Parameter(1);
Node* const end = a->Parameter(2);
Node* const context = a->Parameter(5);
// Check that {receiver} is coercible to Object and convert it to a String.
Node* const string =
a->ToThisString(context, receiver, "String.prototype.substring");
Node* const length = a->LoadStringLength(string);
// Conversion and bounds-checks for {start}.
var_start.Bind(ToSmiBetweenZeroAnd(a, context, start, length));
// Conversion and bounds-checks for {end}.
{
var_end.Bind(length);
a->GotoIf(a->WordEqual(end, a->UndefinedConstant()), &out);
var_end.Bind(ToSmiBetweenZeroAnd(a, context, end, length));
Label if_endislessthanstart(a);
a->Branch(a->SmiLessThan(var_end.value(), var_start.value()),
&if_endislessthanstart, &out);
a->Bind(&if_endislessthanstart);
{
Node* const tmp = var_end.value();
var_end.Bind(var_start.value());
var_start.Bind(tmp);
a->Goto(&out);
}
}
a->Bind(&out);
{
Node* result =
a->SubString(context, string, var_start.value(), var_end.value());
a->Return(result);
}
}
BUILTIN(StringPrototypeStartsWith) {
HandleScope handle_scope(isolate);
TO_THIS_STRING(str, "String.prototype.startsWith");
// Check if the search string is a regExp and fail if it is.
Handle<Object> search = args.atOrUndefined(isolate, 1);
Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search);
if (is_reg_exp.IsNothing()) {
DCHECK(isolate->has_pending_exception());
return isolate->heap()->exception();
}
if (is_reg_exp.FromJust()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kFirstArgumentNotRegExp,
isolate->factory()->NewStringFromStaticChars(
"String.prototype.startsWith")));
}
Handle<String> search_string;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, search_string,
Object::ToString(isolate, search));
Handle<Object> position = args.atOrUndefined(isolate, 2);
int start;
if (position->IsUndefined(isolate)) {
start = 0;
} else {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, position,
Object::ToInteger(isolate, position));
double index = std::max(position->Number(), 0.0);
index = std::min(index, static_cast<double>(str->length()));
start = static_cast<uint32_t>(index);
}
if (start + search_string->length() > str->length()) {
return isolate->heap()->false_value();
}
FlatStringReader str_reader(isolate, String::Flatten(str));
FlatStringReader search_reader(isolate, String::Flatten(search_string));
for (int i = 0; i < search_string->length(); i++) {
if (str_reader.Get(start + i) != search_reader.Get(i)) {
return isolate->heap()->false_value();
}
}
return isolate->heap()->true_value();
}
// 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);
}
void Builtins::Generate_StringPrototypeIterator(CodeStubAssembler* assembler) {
typedef compiler::Node Node;
Node* receiver = assembler->Parameter(0);
Node* context = assembler->Parameter(3);
Node* string = assembler->ToThisString(context, receiver,
"String.prototype[Symbol.iterator]");
Node* native_context = assembler->LoadNativeContext(context);
Node* map = assembler->LoadFixedArrayElement(
native_context,
assembler->IntPtrConstant(Context::STRING_ITERATOR_MAP_INDEX), 0,
CodeStubAssembler::INTPTR_PARAMETERS);
Node* iterator = assembler->Allocate(JSStringIterator::kSize);
assembler->StoreMapNoWriteBarrier(iterator, map);
assembler->StoreObjectFieldRoot(iterator, JSValue::kPropertiesOffset,
Heap::kEmptyFixedArrayRootIndex);
assembler->StoreObjectFieldRoot(iterator, JSObject::kElementsOffset,
Heap::kEmptyFixedArrayRootIndex);
assembler->StoreObjectFieldNoWriteBarrier(
iterator, JSStringIterator::kStringOffset, string);
Node* index = assembler->SmiConstant(Smi::kZero);
assembler->StoreObjectFieldNoWriteBarrier(
iterator, JSStringIterator::kNextIndexOffset, index);
assembler->Return(iterator);
}
namespace {
// Return the |word32| codepoint at {index}. Supports SeqStrings and
// ExternalStrings.
compiler::Node* LoadSurrogatePairInternal(CodeStubAssembler* assembler,
compiler::Node* string,
compiler::Node* length,
compiler::Node* index,
UnicodeEncoding encoding) {
typedef CodeStubAssembler::Label Label;
typedef compiler::Node Node;
typedef CodeStubAssembler::Variable Variable;
Label handle_surrogate_pair(assembler), return_result(assembler);
Variable var_result(assembler, MachineRepresentation::kWord32);
Variable var_trail(assembler, MachineRepresentation::kWord16);
var_result.Bind(assembler->StringCharCodeAt(string, index));
var_trail.Bind(assembler->Int32Constant(0));
assembler->GotoIf(assembler->Word32NotEqual(
assembler->Word32And(var_result.value(),
assembler->Int32Constant(0xFC00)),
assembler->Int32Constant(0xD800)),
&return_result);
Node* next_index =
assembler->SmiAdd(index, assembler->SmiConstant(Smi::FromInt(1)));
assembler->GotoUnless(assembler->SmiLessThan(next_index, length),
&return_result);
var_trail.Bind(assembler->StringCharCodeAt(string, next_index));
assembler->Branch(assembler->Word32Equal(
assembler->Word32And(var_trail.value(),
assembler->Int32Constant(0xFC00)),
assembler->Int32Constant(0xDC00)),
&handle_surrogate_pair, &return_result);
assembler->Bind(&handle_surrogate_pair);
{
Node* lead = var_result.value();
Node* trail = var_trail.value();
// Check that this path is only taken if a surrogate pair is found
CSA_SLOW_ASSERT(assembler, assembler->Uint32GreaterThanOrEqual(
lead, assembler->Int32Constant(0xD800)));
CSA_SLOW_ASSERT(assembler, assembler->Uint32LessThan(
lead, assembler->Int32Constant(0xDC00)));
CSA_SLOW_ASSERT(assembler, assembler->Uint32GreaterThanOrEqual(
trail, assembler->Int32Constant(0xDC00)));
CSA_SLOW_ASSERT(assembler, assembler->Uint32LessThan(
trail, assembler->Int32Constant(0xE000)));
switch (encoding) {
case UnicodeEncoding::UTF16:
var_result.Bind(assembler->WordOr(
// Need to swap the order for big-endian platforms
#if V8_TARGET_BIG_ENDIAN
assembler->WordShl(lead, assembler->Int32Constant(16)), trail));
#else
assembler->WordShl(trail, assembler->Int32Constant(16)), lead));
#endif
break;
case UnicodeEncoding::UTF32: {
// Convert UTF16 surrogate pair into |word32| code point, encoded as
// UTF32.
Node* surrogate_offset =
assembler->Int32Constant(0x10000 - (0xD800 << 10) - 0xDC00);
// (lead << 10) + trail + SURROGATE_OFFSET
var_result.Bind(assembler->Int32Add(
assembler->WordShl(lead, assembler->Int32Constant(10)),
assembler->Int32Add(trail, surrogate_offset)));
break;
}
}
assembler->Goto(&return_result);
}
assembler->Bind(&return_result);
return var_result.value();
}
compiler::Node* LoadSurrogatePairAt(CodeStubAssembler* assembler,
compiler::Node* string,
compiler::Node* length,
compiler::Node* index) {
return LoadSurrogatePairInternal(assembler, string, length, index,
UnicodeEncoding::UTF16);
}
} // namespace
void Builtins::Generate_StringIteratorPrototypeNext(
CodeStubAssembler* assembler) {
typedef CodeStubAssembler::Label Label;
typedef compiler::Node Node;
typedef CodeStubAssembler::Variable Variable;
Variable var_value(assembler, MachineRepresentation::kTagged);
Variable var_done(assembler, MachineRepresentation::kTagged);
var_value.Bind(assembler->UndefinedConstant());
var_done.Bind(assembler->BooleanConstant(true));
Label throw_bad_receiver(assembler), next_codepoint(assembler),
return_result(assembler);
Node* iterator = assembler->Parameter(0);
Node* context = assembler->Parameter(3);
assembler->GotoIf(assembler->TaggedIsSmi(iterator), &throw_bad_receiver);
assembler->GotoUnless(
assembler->WordEqual(assembler->LoadInstanceType(iterator),
assembler->Int32Constant(JS_STRING_ITERATOR_TYPE)),
&throw_bad_receiver);
Node* string =
assembler->LoadObjectField(iterator, JSStringIterator::kStringOffset);
Node* position =
assembler->LoadObjectField(iterator, JSStringIterator::kNextIndexOffset);
Node* length = assembler->LoadObjectField(string, String::kLengthOffset);
assembler->Branch(assembler->SmiLessThan(position, length), &next_codepoint,
&return_result);
assembler->Bind(&next_codepoint);
{
Node* ch = LoadSurrogatePairAt(assembler, string, length, position);
Node* value = assembler->StringFromCodePoint(ch, UnicodeEncoding::UTF16);
var_value.Bind(value);
Node* length = assembler->LoadObjectField(value, String::kLengthOffset);
assembler->StoreObjectFieldNoWriteBarrier(
iterator, JSStringIterator::kNextIndexOffset,
assembler->SmiAdd(position, length));
var_done.Bind(assembler->BooleanConstant(false));
assembler->Goto(&return_result);
}
assembler->Bind(&return_result);
{
Node* native_context = assembler->LoadNativeContext(context);
Node* map = assembler->LoadFixedArrayElement(
native_context,
assembler->IntPtrConstant(Context::ITERATOR_RESULT_MAP_INDEX), 0,
CodeStubAssembler::INTPTR_PARAMETERS);
Node* result = assembler->Allocate(JSIteratorResult::kSize);
assembler->StoreMapNoWriteBarrier(result, map);
assembler->StoreObjectFieldRoot(result, JSIteratorResult::kPropertiesOffset,
Heap::kEmptyFixedArrayRootIndex);
assembler->StoreObjectFieldRoot(result, JSIteratorResult::kElementsOffset,
Heap::kEmptyFixedArrayRootIndex);
assembler->StoreObjectFieldNoWriteBarrier(
result, JSIteratorResult::kValueOffset, var_value.value());
assembler->StoreObjectFieldNoWriteBarrier(
result, JSIteratorResult::kDoneOffset, var_done.value());
assembler->Return(result);
}
assembler->Bind(&throw_bad_receiver);
{
// The {receiver} is not a valid JSGeneratorObject.
Node* result = assembler->CallRuntime(
Runtime::kThrowIncompatibleMethodReceiver, context,
assembler->HeapConstant(assembler->factory()->NewStringFromAsciiChecked(
"String Iterator.prototype.next", TENURED)),
iterator);
assembler->Return(result); // Never reached.
}
}
} // namespace internal
} // namespace v8