Commit cb19ecd6 authored by jgruber's avatar jgruber Committed by Commit bot

[string] Migrate String.prototype.{split,replace} to TF

BUG=

Review-Url: https://codereview.chromium.org/2663803002
Cr-Original-Commit-Position: refs/heads/master@{#42881}
Committed: https://chromium.googlesource.com/v8/v8/+/65ad1e35d9a97c1126a55cc9d3014598fd224259
Review-Url: https://codereview.chromium.org/2663803002
Cr-Commit-Position: refs/heads/master@{#42883}
parent 2517b79c
......@@ -1001,6 +1001,7 @@ v8_source_set("v8_base") {
"src/builtins/builtins-proxy.cc",
"src/builtins/builtins-reflect.cc",
"src/builtins/builtins-regexp.cc",
"src/builtins/builtins-regexp.h",
"src/builtins/builtins-sharedarraybuffer.cc",
"src/builtins/builtins-string.cc",
"src/builtins/builtins-symbol.cc",
......
......@@ -1587,6 +1587,10 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Builtins::kStringPrototypeLocaleCompare, 1, true);
SimpleInstallFunction(prototype, "normalize",
Builtins::kStringPrototypeNormalize, 0, false);
SimpleInstallFunction(prototype, "split", Builtins::kStringPrototypeSplit,
2, true);
SimpleInstallFunction(prototype, "replace",
Builtins::kStringPrototypeReplace, 2, true);
SimpleInstallFunction(prototype, "substr", Builtins::kStringPrototypeSubstr,
2, true);
SimpleInstallFunction(prototype, "substring",
......@@ -3800,7 +3804,7 @@ bool Genesis::InstallNatives(GlobalContextType context_type) {
DCHECK(JSObject::cast(
string_function->initial_map()->prototype())->HasFastProperties());
native_context()->set_string_function_prototype_map(
HeapObject::cast(string_function->initial_map()->prototype())->map());
HeapObject::cast(string_function->map()->prototype())->map());
Handle<JSGlobalObject> global_object =
handle(native_context()->global_object());
......
......@@ -2,6 +2,8 @@
// 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.h"
#include "src/builtins/builtins-constructor.h"
#include "src/builtins/builtins-utils.h"
#include "src/builtins/builtins.h"
......@@ -14,82 +16,8 @@
namespace v8 {
namespace internal {
typedef compiler::Node Node;
typedef CodeStubAssembler::ParameterMode ParameterMode;
typedef compiler::CodeAssemblerState CodeAssemblerState;
class RegExpBuiltinsAssembler : public CodeStubAssembler {
public:
explicit RegExpBuiltinsAssembler(CodeAssemblerState* state)
: CodeStubAssembler(state) {}
protected:
Node* FastLoadLastIndex(Node* regexp);
Node* SlowLoadLastIndex(Node* context, Node* regexp);
Node* LoadLastIndex(Node* context, Node* regexp, bool is_fastpath);
void FastStoreLastIndex(Node* regexp, Node* value);
void SlowStoreLastIndex(Node* context, Node* regexp, Node* value);
void StoreLastIndex(Node* context, Node* regexp, Node* value,
bool is_fastpath);
Node* ConstructNewResultFromMatchInfo(Node* const context, Node* const regexp,
Node* const match_info,
Node* const string);
Node* RegExpPrototypeExecBodyWithoutResult(Node* const context,
Node* const regexp,
Node* const string,
Label* if_didnotmatch,
const bool is_fastpath);
Node* RegExpPrototypeExecBody(Node* const context, Node* const regexp,
Node* const string, const bool is_fastpath);
Node* ThrowIfNotJSReceiver(Node* context, Node* maybe_receiver,
MessageTemplate::Template msg_template,
char const* method_name);
Node* IsInitialRegExpMap(Node* context, Node* map);
void BranchIfFastRegExp(Node* context, Node* map, Label* if_isunmodified,
Label* if_ismodified);
void BranchIfFastRegExpResult(Node* context, Node* map,
Label* if_isunmodified, Label* if_ismodified);
Node* FlagsGetter(Node* const context, Node* const regexp, bool is_fastpath);
Node* FastFlagGetter(Node* const regexp, JSRegExp::Flag flag);
Node* SlowFlagGetter(Node* const context, Node* const regexp,
JSRegExp::Flag flag);
Node* FlagGetter(Node* const context, Node* const regexp, JSRegExp::Flag flag,
bool is_fastpath);
void FlagGetter(JSRegExp::Flag flag, v8::Isolate::UseCounterFeature counter,
const char* method_name);
Node* IsRegExp(Node* const context, Node* const maybe_receiver);
Node* RegExpInitialize(Node* const context, Node* const regexp,
Node* const maybe_pattern, Node* const maybe_flags);
Node* RegExpExec(Node* context, Node* regexp, Node* string);
Node* AdvanceStringIndex(Node* const string, Node* const index,
Node* const is_unicode);
void RegExpPrototypeMatchBody(Node* const context, Node* const regexp,
Node* const string, const bool is_fastpath);
void RegExpPrototypeSearchBodyFast(Node* const context, Node* const regexp,
Node* const string);
void RegExpPrototypeSearchBodySlow(Node* const context, Node* const regexp,
Node* const string);
void RegExpPrototypeSplitBody(Node* const context, Node* const regexp,
Node* const string, Node* const limit);
Node* ReplaceGlobalCallableFastPath(Node* context, Node* regexp, Node* string,
Node* replace_callable);
Node* ReplaceSimpleStringFastPath(Node* context, Node* regexp, Node* string,
Node* replace_string);
};
// -----------------------------------------------------------------------------
// ES6 section 21.2 RegExp Objects
......
// 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_REGEXP_H_
#define V8_BUILTINS_BUILTINS_REGEXP_H_
#include "src/code-stub-assembler.h"
namespace v8 {
namespace internal {
typedef compiler::Node Node;
typedef compiler::CodeAssemblerState CodeAssemblerState;
typedef compiler::CodeAssemblerLabel CodeAssemblerLabel;
class RegExpBuiltinsAssembler : public CodeStubAssembler {
public:
explicit RegExpBuiltinsAssembler(CodeAssemblerState* state)
: CodeStubAssembler(state) {}
void BranchIfFastRegExp(Node* context, Node* map, Label* if_isunmodified,
Label* if_ismodified);
protected:
Node* FastLoadLastIndex(Node* regexp);
Node* SlowLoadLastIndex(Node* context, Node* regexp);
Node* LoadLastIndex(Node* context, Node* regexp, bool is_fastpath);
void FastStoreLastIndex(Node* regexp, Node* value);
void SlowStoreLastIndex(Node* context, Node* regexp, Node* value);
void StoreLastIndex(Node* context, Node* regexp, Node* value,
bool is_fastpath);
Node* ConstructNewResultFromMatchInfo(Node* const context, Node* const regexp,
Node* const match_info,
Node* const string);
Node* RegExpPrototypeExecBodyWithoutResult(Node* const context,
Node* const regexp,
Node* const string,
Label* if_didnotmatch,
const bool is_fastpath);
Node* RegExpPrototypeExecBody(Node* const context, Node* const regexp,
Node* const string, const bool is_fastpath);
Node* ThrowIfNotJSReceiver(Node* context, Node* maybe_receiver,
MessageTemplate::Template msg_template,
char const* method_name);
Node* IsInitialRegExpMap(Node* context, Node* map);
void BranchIfFastRegExpResult(Node* context, Node* map,
Label* if_isunmodified, Label* if_ismodified);
Node* FlagsGetter(Node* const context, Node* const regexp, bool is_fastpath);
Node* FastFlagGetter(Node* const regexp, JSRegExp::Flag flag);
Node* SlowFlagGetter(Node* const context, Node* const regexp,
JSRegExp::Flag flag);
Node* FlagGetter(Node* const context, Node* const regexp, JSRegExp::Flag flag,
bool is_fastpath);
void FlagGetter(JSRegExp::Flag flag, v8::Isolate::UseCounterFeature counter,
const char* method_name);
Node* IsRegExp(Node* const context, Node* const maybe_receiver);
Node* RegExpInitialize(Node* const context, Node* const regexp,
Node* const maybe_pattern, Node* const maybe_flags);
Node* RegExpExec(Node* context, Node* regexp, Node* string);
Node* AdvanceStringIndex(Node* const string, Node* const index,
Node* const is_unicode);
void RegExpPrototypeMatchBody(Node* const context, Node* const regexp,
Node* const string, const bool is_fastpath);
void RegExpPrototypeSearchBodyFast(Node* const context, Node* const regexp,
Node* const string);
void RegExpPrototypeSearchBodySlow(Node* const context, Node* const regexp,
Node* const string);
void RegExpPrototypeSplitBody(Node* const context, Node* const regexp,
Node* const string, Node* const limit);
Node* ReplaceGlobalCallableFastPath(Node* context, Node* regexp, Node* string,
Node* replace_callable);
Node* ReplaceSimpleStringFastPath(Node* context, Node* regexp, Node* string,
Node* replace_string);
};
} // namespace internal
} // namespace v8
#endif // V8_BUILTINS_BUILTINS_REGEXP_H_
This diff is collapsed.
......@@ -748,6 +748,10 @@ class Isolate;
CPP(StringPrototypeLocaleCompare) \
/* ES6 section 21.1.3.12 String.prototype.normalize ( [form] ) */ \
CPP(StringPrototypeNormalize) \
/* ES6 section 21.1.3.16 String.prototype.replace ( search, replace ) */ \
TFJ(StringPrototypeReplace, 2) \
/* ES6 section 21.1.3.19 String.prototype.split ( separator, limit ) */ \
TFJ(StringPrototypeSplit, 2) \
/* ES6 section B.2.3.1 String.prototype.substr ( start, length ) */ \
TFJ(StringPrototypeSubstr, 2) \
/* ES6 section 21.1.3.19 String.prototype.substring ( start, end ) */ \
......
......@@ -9,21 +9,9 @@
// -------------------------------------------------------------------
// Imports
var ArrayJoin;
var GlobalRegExp = global.RegExp;
var GlobalString = global.String;
var MaxSimple;
var MinSimple;
var matchSymbol = utils.ImportNow("match_symbol");
var replaceSymbol = utils.ImportNow("replace_symbol");
var searchSymbol = utils.ImportNow("search_symbol");
var splitSymbol = utils.ImportNow("split_symbol");
utils.Import(function(from) {
ArrayJoin = from.ArrayJoin;
MaxSimple = from.MaxSimple;
MinSimple = from.MinSimple;
});
//-------------------------------------------------------------------
......@@ -58,154 +46,6 @@ function StringMatchJS(pattern) {
return regexp[matchSymbol](subject);
}
// ES#sec-getsubstitution
// GetSubstitution(matched, str, position, captures, replacement)
// Expand the $-expressions in the string and return a new string with
// the result.
function GetSubstitution(matched, string, position, captures, replacement) {
var matchLength = matched.length;
var stringLength = string.length;
var capturesLength = captures.length;
var tailPos = position + matchLength;
var result = "";
var pos, expansion, peek, next, scaledIndex, advance, newScaledIndex;
var next = %StringIndexOf(replacement, '$', 0);
if (next < 0) {
result += replacement;
return result;
}
if (next > 0) result += %_SubString(replacement, 0, next);
while (true) {
expansion = '$';
pos = next + 1;
if (pos < replacement.length) {
peek = %_StringCharCodeAt(replacement, pos);
if (peek == 36) { // $$
++pos;
result += '$';
} else if (peek == 38) { // $& - match
++pos;
result += matched;
} else if (peek == 96) { // $` - prefix
++pos;
result += %_SubString(string, 0, position);
} else if (peek == 39) { // $' - suffix
++pos;
result += %_SubString(string, tailPos, stringLength);
} else if (peek >= 48 && peek <= 57) {
// Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
scaledIndex = (peek - 48);
advance = 1;
if (pos + 1 < replacement.length) {
next = %_StringCharCodeAt(replacement, pos + 1);
if (next >= 48 && next <= 57) {
newScaledIndex = scaledIndex * 10 + ((next - 48));
if (newScaledIndex < capturesLength) {
scaledIndex = newScaledIndex;
advance = 2;
}
}
}
if (scaledIndex != 0 && scaledIndex < capturesLength) {
var capture = captures.at(scaledIndex);
if (!IS_UNDEFINED(capture)) result += capture;
pos += advance;
} else {
result += '$';
}
} else {
result += '$';
}
} else {
result += '$';
}
// Go the the next $ in the replacement.
next = %StringIndexOf(replacement, '$', pos);
// Return if there are no more $ characters in the replacement. If we
// haven't reached the end, we need to append the suffix.
if (next < 0) {
if (pos < replacement.length) {
result += %_SubString(replacement, pos, replacement.length);
}
return result;
}
// Append substring between the previous and the next $ character.
if (next > pos) {
result += %_SubString(replacement, pos, next);
}
}
return result;
}
// ES6, section 21.1.3.14
function StringReplace(search, replace) {
CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace");
// Decision tree for dispatch
// .. regexp search (in src/js/regexp.js, RegExpReplace)
// .... string replace
// ...... non-global search
// ........ empty string replace
// ........ non-empty string replace (with $-expansion)
// ...... global search
// ........ no need to circumvent last match info override
// ........ need to circument last match info override
// .... function replace
// ...... global search
// ...... non-global search
// .. string search
// .... special case that replaces with one single character
// ...... function replace
// ...... string replace (with $-expansion)
if (!IS_NULL_OR_UNDEFINED(search)) {
var replacer = search[replaceSymbol];
if (!IS_UNDEFINED(replacer)) {
return %_Call(replacer, search, this, replace);
}
}
var subject = TO_STRING(this);
search = TO_STRING(search);
if (search.length == 1 &&
subject.length > 0xFF &&
IS_STRING(replace) &&
%StringIndexOf(replace, '$', 0) < 0) {
// Searching by traversing a cons string tree and replace with cons of
// slices works only when the replaced string is a single character, being
// replaced by a simple string and only pays off for long strings.
return %StringReplaceOneCharWithString(subject, search, replace);
}
var start = %StringIndexOf(subject, search, 0);
if (start < 0) return subject;
var end = start + search.length;
var result = %_SubString(subject, 0, start);
// Compute the string to replace with.
if (IS_CALLABLE(replace)) {
result += replace(search, start, subject);
} else {
// In this case, we don't have any capture groups and can get away with
// faking the captures object by simply setting its length to 1.
const captures = { length: 1 };
const matched = %_SubString(subject, start, end);
result += GetSubstitution(matched, subject, start, captures,
TO_STRING(replace));
}
return result + %_SubString(subject, end, subject.length);
}
// ES6 21.1.3.15.
function StringSearch(pattern) {
CHECK_OBJECT_COERCIBLE(this, "String.prototype.search");
......@@ -266,39 +106,6 @@ function StringSlice(start, end) {
return %_SubString(s, start_i, end_i);
}
// ES6 21.1.3.17.
function StringSplitJS(separator, limit) {
CHECK_OBJECT_COERCIBLE(this, "String.prototype.split");
if (!IS_NULL_OR_UNDEFINED(separator)) {
var splitter = separator[splitSymbol];
if (!IS_UNDEFINED(splitter)) {
return %_Call(splitter, separator, this, limit);
}
}
var subject = TO_STRING(this);
limit = (IS_UNDEFINED(limit)) ? kMaxUint32 : TO_UINT32(limit);
var length = subject.length;
var separator_string = TO_STRING(separator);
if (limit === 0) return [];
// ECMA-262 says that if separator is undefined, the result should
// be an array of size 1 containing the entire string.
if (IS_UNDEFINED(separator)) return [subject];
var separator_length = separator_string.length;
// If the separator string is empty then return the elements in the subject.
if (separator_length === 0) return %StringToArray(subject, limit);
return %StringSplit(subject, separator_string, limit);
}
// ECMA-262, 15.5.4.16
function StringToLowerCaseJS() {
CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase");
......@@ -515,10 +322,8 @@ utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [
"concat", StringConcat,
"match", StringMatchJS,
"repeat", StringRepeat,
"replace", StringReplace,
"search", StringSearch,
"slice", StringSlice,
"split", StringSplitJS,
"toLowerCase", StringToLowerCaseJS,
"toLocaleLowerCase", StringToLocaleLowerCase,
"toUpperCase", StringToUpperCaseJS,
......@@ -539,14 +344,4 @@ utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [
"sup", StringSup
]);
// -------------------------------------------------------------------
// Exports
utils.Export(function(to) {
to.StringMatch = StringMatchJS;
to.StringReplace = StringReplace;
to.StringSlice = StringSlice;
to.StringSplit = StringSplitJS;
});
})
......@@ -13,6 +13,44 @@
namespace v8 {
namespace internal {
RUNTIME_FUNCTION(Runtime_GetSubstitution) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
CONVERT_ARG_HANDLE_CHECKED(String, matched, 0);
CONVERT_ARG_HANDLE_CHECKED(String, subject, 1);
CONVERT_SMI_ARG_CHECKED(position, 2);
CONVERT_ARG_HANDLE_CHECKED(String, replacement, 3);
// A simple match without captures.
class SimpleMatch : public String::Match {
public:
SimpleMatch(Handle<String> match, Handle<String> prefix,
Handle<String> suffix)
: match_(match), prefix_(prefix), suffix_(suffix) {}
Handle<String> GetMatch() override { return match_; }
MaybeHandle<String> GetCapture(int i, bool* capture_exists) override {
*capture_exists = false;
return match_; // Return arbitrary string handle.
}
Handle<String> GetPrefix() override { return prefix_; }
Handle<String> GetSuffix() override { return suffix_; }
int CaptureCount() override { return 0; }
private:
Handle<String> match_, prefix_, suffix_;
};
Handle<String> prefix =
isolate->factory()->NewSubString(subject, 0, position);
Handle<String> suffix = isolate->factory()->NewSubString(
subject, position + matched->length(), subject->length());
SimpleMatch match(matched, prefix, suffix);
RETURN_RESULT_OR_FAILURE(
isolate, String::GetSubstitution(isolate, &match, replacement));
}
// This may return an empty MaybeHandle if an exception is thrown or
// we abort due to reaching the recursion limit.
MaybeHandle<String> StringReplaceOneCharWithString(
......
......@@ -819,6 +819,7 @@ namespace internal {
F(Bool8x16NotEqual, 2, 1)
#define FOR_EACH_INTRINSIC_STRINGS(F) \
F(GetSubstitution, 4, 1) \
F(StringReplaceOneCharWithString, 3, 1) \
F(StringIndexOf, 3, 1) \
F(StringIndexOfUnchecked, 3, 1) \
......
......@@ -507,6 +507,7 @@
'builtins/builtins-proxy.cc',
'builtins/builtins-reflect.cc',
'builtins/builtins-regexp.cc',
'builtins/builtins-regexp.h',
'builtins/builtins-sharedarraybuffer.cc',
'builtins/builtins-string.cc',
'builtins/builtins-symbol.cc',
......
......@@ -53,7 +53,10 @@ function listener(event, exec_state, event_data, data) {
if (f == "normalize") continue;
if (f == "match") continue;
if (f == "search") continue;
if (f == "split") continue;
if (f == "split" || f == "replace") {
fail(`'abcd'.${f}(2)`);
continue;
}
success("abcd"[f](2), `"abcd".${f}(2);`);
}
}
......
......@@ -283,3 +283,16 @@ function testIndices59(re) {
testIndices59(new RegExp(regexp59pattern));
testIndices59(new RegExp(regexp59pattern, "g"));
// Test that ToString(replace) is called.
let replace_tostring_count = 0;
const fake_replacer = {
[Symbol.toPrimitive]: () => { replace_tostring_count++; return "b"; }
};
"a".replace("x", fake_replacer);
assertEquals(1, replace_tostring_count);
"a".replace("a", fake_replacer);
assertEquals(2, replace_tostring_count);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment