[string] port String.p.startsWith to torque
Port String.prototype.startsWith from a CPP builtin to a Torque builtin. Spec: https://tc39.github.io/ecma262/#sec-string.prototype.startswith Bug: v8:8400 Change-Id: I51aff0b3a4126c17ab4f89763019fd7e4ba665d9 Reviewed-on: https://chromium-review.googlesource.com/c/1361340 Commit-Queue: Jakob Gruber <jgruber@chromium.org> Reviewed-by: Jakob Gruber <jgruber@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org> Cr-Commit-Position: refs/heads/master@{#59355}
This commit is contained in:
parent
1358917e51
commit
44ffcca488
12
BUILD.gn
12
BUILD.gn
@ -882,6 +882,7 @@ torque_files = [
|
||||
"src/builtins/extras-utils.tq",
|
||||
"src/builtins/object-fromentries.tq",
|
||||
"src/builtins/iterator.tq",
|
||||
"src/builtins/string-startswith.tq",
|
||||
"src/builtins/typed-array.tq",
|
||||
"src/builtins/typed-array-createtypedarray.tq",
|
||||
"test/torque/test-torque.tq",
|
||||
@ -889,7 +890,6 @@ torque_files = [
|
||||
]
|
||||
|
||||
torque_namespaces = [
|
||||
"base",
|
||||
"arguments",
|
||||
"array",
|
||||
"array-copywithin",
|
||||
@ -903,14 +903,16 @@ torque_namespaces = [
|
||||
"array-splice",
|
||||
"array-unshift",
|
||||
"array-lastindexof",
|
||||
"base",
|
||||
"collections",
|
||||
"iterator",
|
||||
"object",
|
||||
"typed-array",
|
||||
"typed-array-createtypedarray",
|
||||
"data-view",
|
||||
"extras-utils",
|
||||
"iterator",
|
||||
"object",
|
||||
"string",
|
||||
"test",
|
||||
"typed-array",
|
||||
"typed-array-createtypedarray",
|
||||
]
|
||||
|
||||
action("run_torque") {
|
||||
|
@ -61,6 +61,10 @@ type Number = Smi | HeapNumber;
|
||||
type BigInt extends HeapObject generates 'TNode<BigInt>';
|
||||
type Numeric = Number | BigInt;
|
||||
|
||||
// A direct string can be accessed directly through CSA without going into the
|
||||
// C++ runtime. See also: ToDirectStringAssembler.
|
||||
type DirectString extends String generates 'TNode<String>';
|
||||
|
||||
type RootIndex generates 'TNode<Int32T>' constexpr 'RootIndex';
|
||||
|
||||
type Map extends HeapObject generates 'TNode<Map>';
|
||||
@ -321,6 +325,8 @@ const kIteratorValueNotAnObject: constexpr MessageTemplate
|
||||
generates 'MessageTemplate::kIteratorValueNotAnObject';
|
||||
const kNotIterable: constexpr MessageTemplate
|
||||
generates 'MessageTemplate::kNotIterable';
|
||||
const kFirstArgumentNotRegExp: constexpr MessageTemplate
|
||||
generates 'MessageTemplate::kFirstArgumentNotRegExp';
|
||||
|
||||
const kMaxArrayIndex:
|
||||
constexpr uint32 generates 'JSArray::kMaxArrayIndex';
|
||||
@ -493,6 +499,8 @@ extern macro LoadAndUntagToWord32Root(constexpr RootIndex): int32;
|
||||
|
||||
extern runtime StringEqual(Context, String, String): Oddball;
|
||||
extern builtin StringLessThan(Context, String, String): Boolean;
|
||||
extern macro StringCharCodeAt(String, intptr): int32;
|
||||
extern runtime StringStartsWith(Context, String, String, Number): Boolean;
|
||||
|
||||
extern macro StrictEqual(Object, Object): Boolean;
|
||||
extern macro SmiLexicographicCompare(Smi, Smi): Smi;
|
||||
@ -670,6 +678,7 @@ extern operator '.instanceType' macro LoadInstanceType(HeapObject):
|
||||
InstanceType;
|
||||
|
||||
extern operator '.length' macro LoadStringLengthAsWord(String): intptr;
|
||||
extern operator '.length_smi' macro LoadStringLengthAsSmi(String): Smi;
|
||||
|
||||
extern operator '.length' macro GetArgumentsLength(constexpr Arguments): intptr;
|
||||
extern operator '[]' macro GetArgumentValue(
|
||||
@ -692,6 +701,8 @@ extern macro TaggedToSmi(Object): Smi
|
||||
labels CastError;
|
||||
extern macro TaggedToPositiveSmi(Object): PositiveSmi
|
||||
labels CastError;
|
||||
extern macro TaggedToDirectString(Object): DirectString
|
||||
labels CastError;
|
||||
extern macro HeapObjectToJSArray(HeapObject): JSArray
|
||||
labels CastError;
|
||||
extern macro HeapObjectToCallable(HeapObject): Callable
|
||||
@ -811,6 +822,11 @@ Cast<String>(o: HeapObject): String
|
||||
return HeapObjectToString(o) otherwise CastError;
|
||||
}
|
||||
|
||||
Cast<DirectString>(o: HeapObject): DirectString
|
||||
labels CastError {
|
||||
return TaggedToDirectString(o) otherwise CastError;
|
||||
}
|
||||
|
||||
Cast<Constructor>(o: HeapObject): Constructor
|
||||
labels CastError {
|
||||
return HeapObjectToConstructor(o) otherwise CastError;
|
||||
|
@ -1118,8 +1118,6 @@ namespace internal {
|
||||
SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
|
||||
/* ES6 #sec-string.prototype.sup */ \
|
||||
TFJ(StringPrototypeSup, 0, kReceiver) \
|
||||
/* ES6 #sec-string.prototype.startswith */ \
|
||||
CPP(StringPrototypeStartsWith) \
|
||||
/* ES6 #sec-string.prototype.tostring */ \
|
||||
TFJ(StringPrototypeToString, 0, kReceiver) \
|
||||
TFJ(StringPrototypeTrim, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
|
||||
|
@ -30,6 +30,8 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
|
||||
TNode<Object> RegExpCreate(TNode<Context> context, TNode<Map> initial_map,
|
||||
TNode<Object> regexp_string, TNode<String> flags);
|
||||
|
||||
TNode<BoolT> IsRegExp(TNode<Context> context, TNode<Object> maybe_receiver);
|
||||
|
||||
protected:
|
||||
TNode<Smi> SmiZero();
|
||||
TNode<IntPtrT> IntPtrZero();
|
||||
@ -112,8 +114,6 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
|
||||
void FlagGetter(Node* context, Node* receiver, JSRegExp::Flag flag,
|
||||
int counter, const char* method_name);
|
||||
|
||||
TNode<BoolT> IsRegExp(TNode<Context> context, TNode<Object> maybe_receiver);
|
||||
|
||||
Node* RegExpInitialize(Node* const context, Node* const regexp,
|
||||
Node* const maybe_pattern, Node* const maybe_flags);
|
||||
|
||||
|
@ -290,53 +290,6 @@ BUILTIN(StringPrototypeNormalize) {
|
||||
}
|
||||
#endif // !V8_INTL_SUPPORT
|
||||
|
||||
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 ReadOnlyRoots(isolate).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));
|
||||
start = str->ToValidIndex(*position);
|
||||
}
|
||||
|
||||
if (start + search_string->length() > str->length()) {
|
||||
return ReadOnlyRoots(isolate).false_value();
|
||||
}
|
||||
|
||||
FlatStringReader str_reader(isolate, String::Flatten(isolate, str));
|
||||
FlatStringReader search_reader(isolate,
|
||||
String::Flatten(isolate, search_string));
|
||||
|
||||
for (int i = 0; i < search_string->length(); i++) {
|
||||
if (str_reader.Get(start + i) != search_reader.Get(i)) {
|
||||
return ReadOnlyRoots(isolate).false_value();
|
||||
}
|
||||
}
|
||||
return ReadOnlyRoots(isolate).true_value();
|
||||
}
|
||||
|
||||
#ifndef V8_INTL_SUPPORT
|
||||
namespace {
|
||||
|
90
src/builtins/string-startswith.tq
Normal file
90
src/builtins/string-startswith.tq
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright 2018 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-regexp-gen.h'
|
||||
|
||||
namespace string {
|
||||
extern macro RegExpBuiltinsAssembler::IsRegExp(implicit context:
|
||||
Context)(Object): bool;
|
||||
|
||||
// TODO(ryzokuken): Add RequireObjectCoercible to base.tq and update callsites
|
||||
macro RequireObjectCoercible(implicit context: Context)(argument: Object):
|
||||
Object {
|
||||
if (IsNullOrUndefined(argument)) {
|
||||
ThrowTypeError(
|
||||
context, kCalledOnNullOrUndefined, 'String.prototype.startsWith');
|
||||
}
|
||||
return argument;
|
||||
}
|
||||
|
||||
// https://tc39.github.io/ecma262/#sec-string.prototype.startswith
|
||||
transitioning javascript builtin StringPrototypeStartsWith(
|
||||
context: Context, receiver: Object, ...arguments): Boolean {
|
||||
const searchString: Object = arguments[0];
|
||||
const position: Object = arguments[1];
|
||||
|
||||
// 1. Let O be ? RequireObjectCoercible(this value).
|
||||
const object: Object = RequireObjectCoercible(receiver);
|
||||
|
||||
// 2. Let S be ? ToString(O).
|
||||
const string: String = ToString_Inline(context, object);
|
||||
|
||||
// 3. Let isRegExp be ? IsRegExp(searchString).
|
||||
// 4. If isRegExp is true, throw a TypeError exception.
|
||||
if (IsRegExp(searchString)) {
|
||||
ThrowTypeError(
|
||||
context, kFirstArgumentNotRegExp, 'String.prototype.startsWith');
|
||||
}
|
||||
|
||||
// 5. Let searchStr be ? ToString(searchString).
|
||||
const searchStr: String = ToString_Inline(context, searchString);
|
||||
|
||||
// 6. Let pos be ? ToInteger(position).
|
||||
const pos: Number = ToInteger_Inline(context, position);
|
||||
|
||||
// 7. Assert: If position is undefined, then pos is 0.
|
||||
// 8. Let len be the length of S.
|
||||
const len: Number = string.length_smi;
|
||||
|
||||
// 9. Let start be min(max(pos, 0), len).
|
||||
const start: Number = NumberMin(NumberMax(pos, 0), len);
|
||||
|
||||
// 10. Let searchLength be the length of searchStr.
|
||||
const searchLength: Smi = searchStr.length_smi;
|
||||
|
||||
// 11. If searchLength + start is greater than len, return false.
|
||||
if (searchLength + start > len) return False;
|
||||
|
||||
// 12. If the sequence of code units of S starting at start of length
|
||||
// searchLength is the same as the full code unit sequence of searchStr,
|
||||
// return true.
|
||||
|
||||
try {
|
||||
// Fast Path: If both strings are direct and relevant indices are Smis.
|
||||
const directString = Cast<DirectString>(string) otherwise Slow;
|
||||
const directSearchStr = Cast<DirectString>(searchStr) otherwise Slow;
|
||||
const stringIndexSmi: Smi = Cast<Smi>(start) otherwise Slow;
|
||||
|
||||
let searchIndex: intptr = 0;
|
||||
let stringIndex: intptr = Convert<intptr>(stringIndexSmi);
|
||||
const searchLengthInteger: intptr = Convert<intptr>(searchLength);
|
||||
|
||||
while (searchIndex < searchLengthInteger) {
|
||||
if (StringCharCodeAt(directSearchStr, searchIndex) !=
|
||||
StringCharCodeAt(directString, stringIndex)) {
|
||||
// 13. Otherwise, return false.
|
||||
return False;
|
||||
}
|
||||
|
||||
searchIndex++;
|
||||
stringIndex++;
|
||||
}
|
||||
return True;
|
||||
}
|
||||
label Slow {
|
||||
// Slow Path: If either of the string is indirect, bail into runtime.
|
||||
return StringStartsWith(context, string, searchStr, start);
|
||||
}
|
||||
}
|
||||
}
|
@ -13848,5 +13848,13 @@ void CodeStubAssembler::GotoIfInitialPrototypePropertiesModified(
|
||||
}
|
||||
}
|
||||
|
||||
TNode<String> CodeStubAssembler::TaggedToDirectString(TNode<Object> value,
|
||||
Label* fail) {
|
||||
ToDirectStringAssembler to_direct(state(), value);
|
||||
to_direct.TryToDirect(fail);
|
||||
to_direct.PointerToData(fail);
|
||||
return CAST(value);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -322,6 +322,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
|
||||
return UncheckedCast<Smi>(value);
|
||||
}
|
||||
|
||||
TNode<String> TaggedToDirectString(TNode<Object> value, Label* fail);
|
||||
|
||||
TNode<Number> TaggedToNumber(TNode<Object> value, Label* fail) {
|
||||
GotoIfNot(IsNumber(value), fail);
|
||||
return UncheckedCast<Number>(value);
|
||||
|
@ -713,5 +713,27 @@ RUNTIME_FUNCTION(Runtime_StringMaxLength) {
|
||||
return Smi::FromInt(String::kMaxLength);
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_StringStartsWith) {
|
||||
HandleScope handle_scope(isolate);
|
||||
DCHECK_EQ(3, args.length());
|
||||
CONVERT_ARG_HANDLE_CHECKED(String, string, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(String, search_string, 1);
|
||||
CONVERT_NUMBER_CHECKED(int, start, Int32, args[2]);
|
||||
|
||||
// Check if start + searchLength is in bounds.
|
||||
DCHECK_LE(start + search_string->length(), string->length());
|
||||
|
||||
FlatStringReader string_reader(isolate, String::Flatten(isolate, string));
|
||||
FlatStringReader search_reader(isolate,
|
||||
String::Flatten(isolate, search_string));
|
||||
|
||||
for (int i = 0; i < search_string->length(); i++) {
|
||||
if (string_reader.Get(start + i) != search_reader.Get(i)) {
|
||||
return ReadOnlyRoots(isolate).false_value();
|
||||
}
|
||||
}
|
||||
|
||||
return ReadOnlyRoots(isolate).true_value();
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -421,6 +421,7 @@ namespace internal {
|
||||
F(StringLessThanOrEqual, 2, 1) \
|
||||
F(StringMaxLength, 0, 1) \
|
||||
F(StringReplaceOneCharWithString, 3, 1) \
|
||||
F(StringStartsWith, 3, 1) \
|
||||
F(StringSubstring, 3, 1) \
|
||||
F(StringToArray, 2, 1) \
|
||||
F(StringTrim, 2, 1)
|
||||
|
Loading…
Reference in New Issue
Block a user