aboutsummaryrefslogtreecommitdiff
path: root/src/builtins/builtins-regexp.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/builtins/builtins-regexp.cc')
-rw-r--r--src/builtins/builtins-regexp.cc3265
1 files changed, 1915 insertions, 1350 deletions
diff --git a/src/builtins/builtins-regexp.cc b/src/builtins/builtins-regexp.cc
index 5f8d18be..f76136b8 100644
--- a/src/builtins/builtins-regexp.cc
+++ b/src/builtins/builtins-regexp.cc
@@ -2,10 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "src/builtins/builtins-regexp.h"
+
+#include "src/builtins/builtins-constructor.h"
#include "src/builtins/builtins-utils.h"
#include "src/builtins/builtins.h"
-
#include "src/code-factory.h"
+#include "src/code-stub-assembler.h"
+#include "src/counters.h"
+#include "src/objects-inl.h"
+#include "src/objects/regexp-match-info.h"
#include "src/regexp/jsregexp.h"
#include "src/regexp/regexp-utils.h"
#include "src/string-builder.h"
@@ -13,735 +19,942 @@
namespace v8 {
namespace internal {
-// -----------------------------------------------------------------------------
-// ES6 section 21.2 RegExp Objects
-
-namespace {
-
-Handle<String> PatternFlags(Isolate* isolate, Handle<JSRegExp> regexp) {
- static const int kMaxFlagsLength = 5 + 1; // 5 flags and '\0';
- char flags_string[kMaxFlagsLength];
- int i = 0;
-
- const JSRegExp::Flags flags = regexp->GetFlags();
-
- if ((flags & JSRegExp::kGlobal) != 0) flags_string[i++] = 'g';
- if ((flags & JSRegExp::kIgnoreCase) != 0) flags_string[i++] = 'i';
- if ((flags & JSRegExp::kMultiline) != 0) flags_string[i++] = 'm';
- if ((flags & JSRegExp::kUnicode) != 0) flags_string[i++] = 'u';
- if ((flags & JSRegExp::kSticky) != 0) flags_string[i++] = 'y';
-
- DCHECK_LT(i, kMaxFlagsLength);
- memset(&flags_string[i], '\0', kMaxFlagsLength - i);
-
- return isolate->factory()->NewStringFromAsciiChecked(flags_string);
-}
-
-// ES#sec-regexpinitialize
-// Runtime Semantics: RegExpInitialize ( obj, pattern, flags )
-MUST_USE_RESULT MaybeHandle<JSRegExp> RegExpInitialize(Isolate* isolate,
- Handle<JSRegExp> regexp,
- Handle<Object> pattern,
- Handle<Object> flags) {
- Handle<String> pattern_string;
- if (pattern->IsUndefined(isolate)) {
- pattern_string = isolate->factory()->empty_string();
- } else {
- ASSIGN_RETURN_ON_EXCEPTION(isolate, pattern_string,
- Object::ToString(isolate, pattern), JSRegExp);
- }
-
- Handle<String> flags_string;
- if (flags->IsUndefined(isolate)) {
- flags_string = isolate->factory()->empty_string();
- } else {
- ASSIGN_RETURN_ON_EXCEPTION(isolate, flags_string,
- Object::ToString(isolate, flags), JSRegExp);
- }
-
- // TODO(jgruber): We could avoid the flags back and forth conversions.
- return JSRegExp::Initialize(regexp, pattern_string, flags_string);
-}
-
-} // namespace
-
-// ES#sec-regexp-pattern-flags
-// RegExp ( pattern, flags )
-BUILTIN(RegExpConstructor) {
- HandleScope scope(isolate);
-
- Handle<HeapObject> new_target = args.new_target();
- Handle<Object> pattern = args.atOrUndefined(isolate, 1);
- Handle<Object> flags = args.atOrUndefined(isolate, 2);
-
- Handle<JSFunction> target = isolate->regexp_function();
-
- bool pattern_is_regexp;
- {
- Maybe<bool> maybe_pattern_is_regexp =
- RegExpUtils::IsRegExp(isolate, pattern);
- if (maybe_pattern_is_regexp.IsNothing()) {
- DCHECK(isolate->has_pending_exception());
- return isolate->heap()->exception();
- }
- pattern_is_regexp = maybe_pattern_is_regexp.FromJust();
- }
-
- if (new_target->IsUndefined(isolate)) {
- new_target = target;
-
- // ES6 section 21.2.3.1 step 3.b
- if (pattern_is_regexp && flags->IsUndefined(isolate)) {
- Handle<Object> pattern_constructor;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, pattern_constructor,
- Object::GetProperty(pattern,
- isolate->factory()->constructor_string()));
-
- if (pattern_constructor.is_identical_to(new_target)) {
- return *pattern;
- }
- }
- }
-
- if (pattern->IsJSRegExp()) {
- Handle<JSRegExp> regexp_pattern = Handle<JSRegExp>::cast(pattern);
-
- if (flags->IsUndefined(isolate)) {
- flags = PatternFlags(isolate, regexp_pattern);
- }
- pattern = handle(regexp_pattern->source(), isolate);
- } else if (pattern_is_regexp) {
- Handle<Object> pattern_source;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, pattern_source,
- Object::GetProperty(pattern, isolate->factory()->source_string()));
-
- if (flags->IsUndefined(isolate)) {
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, flags,
- Object::GetProperty(pattern, isolate->factory()->flags_string()));
- }
- pattern = pattern_source;
- }
-
- Handle<JSReceiver> new_target_receiver = Handle<JSReceiver>::cast(new_target);
-
- // TODO(jgruber): Fast-path for target == new_target == unmodified JSRegExp.
-
- Handle<JSObject> object;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, object, JSObject::New(target, new_target_receiver));
- Handle<JSRegExp> regexp = Handle<JSRegExp>::cast(object);
-
- RETURN_RESULT_OR_FAILURE(isolate,
- RegExpInitialize(isolate, regexp, pattern, flags));
-}
-
-BUILTIN(RegExpPrototypeCompile) {
- HandleScope scope(isolate);
- CHECK_RECEIVER(JSRegExp, regexp, "RegExp.prototype.compile");
-
- Handle<Object> pattern = args.atOrUndefined(isolate, 1);
- Handle<Object> flags = args.atOrUndefined(isolate, 2);
-
- if (pattern->IsJSRegExp()) {
- Handle<JSRegExp> pattern_regexp = Handle<JSRegExp>::cast(pattern);
-
- if (!flags->IsUndefined(isolate)) {
- THROW_NEW_ERROR_RETURN_FAILURE(
- isolate, NewTypeError(MessageTemplate::kRegExpFlags));
- }
+typedef CodeStubAssembler::ParameterMode ParameterMode;
- flags = PatternFlags(isolate, pattern_regexp);
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, pattern,
- Object::GetProperty(pattern, isolate->factory()->source_string()));
- }
-
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, regexp, RegExpInitialize(isolate, regexp, pattern, flags));
-
- // Return undefined for compatibility with JSC.
- // See http://crbug.com/585775 for web compat details.
-
- return isolate->heap()->undefined_value();
-}
-namespace {
+// -----------------------------------------------------------------------------
+// ES6 section 21.2 RegExp Objects
-compiler::Node* FastLoadLastIndex(CodeStubAssembler* a, compiler::Node* context,
- compiler::Node* regexp) {
+Node* RegExpBuiltinsAssembler::FastLoadLastIndex(Node* regexp) {
// Load the in-object field.
static const int field_offset =
JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize;
- return a->LoadObjectField(regexp, field_offset);
+ return LoadObjectField(regexp, field_offset);
}
-compiler::Node* SlowLoadLastIndex(CodeStubAssembler* a, compiler::Node* context,
- compiler::Node* regexp) {
+Node* RegExpBuiltinsAssembler::SlowLoadLastIndex(Node* context, Node* regexp) {
// Load through the GetProperty stub.
- typedef compiler::Node Node;
-
- Node* const name =
- a->HeapConstant(a->isolate()->factory()->lastIndex_string());
- Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
- return a->CallStub(getproperty_callable, context, regexp, name);
+ Node* const name = HeapConstant(isolate()->factory()->lastIndex_string());
+ Callable getproperty_callable = CodeFactory::GetProperty(isolate());
+ return CallStub(getproperty_callable, context, regexp, name);
}
-compiler::Node* LoadLastIndex(CodeStubAssembler* a, compiler::Node* context,
- compiler::Node* has_initialmap,
- compiler::Node* regexp) {
- typedef CodeStubAssembler::Variable Variable;
- typedef CodeStubAssembler::Label Label;
-
- Variable var_value(a, MachineRepresentation::kTagged);
-
- Label out(a), if_unmodified(a), if_modified(a);
- a->Branch(has_initialmap, &if_unmodified, &if_modified);
-
- a->Bind(&if_unmodified);
- {
- var_value.Bind(FastLoadLastIndex(a, context, regexp));
- a->Goto(&out);
- }
-
- a->Bind(&if_modified);
- {
- var_value.Bind(SlowLoadLastIndex(a, context, regexp));
- a->Goto(&out);
- }
-
- a->Bind(&out);
- return var_value.value();
+Node* RegExpBuiltinsAssembler::LoadLastIndex(Node* context, Node* regexp,
+ bool is_fastpath) {
+ return is_fastpath ? FastLoadLastIndex(regexp)
+ : SlowLoadLastIndex(context, regexp);
}
// The fast-path of StoreLastIndex when regexp is guaranteed to be an unmodified
// JSRegExp instance.
-void FastStoreLastIndex(CodeStubAssembler* a, compiler::Node* context,
- compiler::Node* regexp, compiler::Node* value) {
+void RegExpBuiltinsAssembler::FastStoreLastIndex(Node* regexp, Node* value) {
// Store the in-object field.
static const int field_offset =
JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize;
- a->StoreObjectField(regexp, field_offset, value);
+ StoreObjectField(regexp, field_offset, value);
}
-void SlowStoreLastIndex(CodeStubAssembler* a, compiler::Node* context,
- compiler::Node* regexp, compiler::Node* value) {
+void RegExpBuiltinsAssembler::SlowStoreLastIndex(Node* context, Node* regexp,
+ Node* value) {
// Store through runtime.
// TODO(ishell): Use SetPropertyStub here once available.
- typedef compiler::Node Node;
-
- Node* const name =
- a->HeapConstant(a->isolate()->factory()->lastIndex_string());
- Node* const language_mode = a->SmiConstant(Smi::FromInt(STRICT));
- a->CallRuntime(Runtime::kSetProperty, context, regexp, name, value,
- language_mode);
+ Node* const name = HeapConstant(isolate()->factory()->lastIndex_string());
+ Node* const language_mode = SmiConstant(Smi::FromInt(STRICT));
+ CallRuntime(Runtime::kSetProperty, context, regexp, name, value,
+ language_mode);
}
-void StoreLastIndex(CodeStubAssembler* a, compiler::Node* context,
- compiler::Node* has_initialmap, compiler::Node* regexp,
- compiler::Node* value) {
- typedef CodeStubAssembler::Label Label;
-
- Label out(a), if_unmodified(a), if_modified(a);
- a->Branch(has_initialmap, &if_unmodified, &if_modified);
-
- a->Bind(&if_unmodified);
- {
- FastStoreLastIndex(a, context, regexp, value);
- a->Goto(&out);
- }
-
- a->Bind(&if_modified);
- {
- SlowStoreLastIndex(a, context, regexp, value);
- a->Goto(&out);
+void RegExpBuiltinsAssembler::StoreLastIndex(Node* context, Node* regexp,
+ Node* value, bool is_fastpath) {
+ if (is_fastpath) {
+ FastStoreLastIndex(regexp, value);
+ } else {
+ SlowStoreLastIndex(context, regexp, value);
}
-
- a->Bind(&out);
}
-compiler::Node* ConstructNewResultFromMatchInfo(Isolate* isolate,
- CodeStubAssembler* a,
- compiler::Node* context,
- compiler::Node* match_info,
- compiler::Node* string) {
- typedef CodeStubAssembler::Variable Variable;
- typedef CodeStubAssembler::Label Label;
- typedef compiler::Node Node;
-
- Label out(a);
-
- CodeStubAssembler::ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS;
- Node* const num_indices = a->SmiUntag(a->LoadFixedArrayElement(
- match_info, a->IntPtrConstant(RegExpMatchInfo::kNumberOfCapturesIndex), 0,
- mode));
- Node* const num_results = a->SmiTag(a->WordShr(num_indices, 1));
- Node* const start = a->LoadFixedArrayElement(
- match_info, a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), 0,
- mode);
- Node* const end = a->LoadFixedArrayElement(
- match_info, a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 1), 0,
- mode);
+Node* RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
+ Node* const context, Node* const regexp, Node* const match_info,
+ Node* const string) {
+ Label named_captures(this), out(this);
+
+ Node* const num_indices = SmiUntag(LoadFixedArrayElement(
+ match_info, RegExpMatchInfo::kNumberOfCapturesIndex));
+ Node* const num_results = SmiTag(WordShr(num_indices, 1));
+ Node* const start =
+ LoadFixedArrayElement(match_info, RegExpMatchInfo::kFirstCaptureIndex);
+ Node* const end = LoadFixedArrayElement(
+ match_info, RegExpMatchInfo::kFirstCaptureIndex + 1);
// Calculate the substring of the first match before creating the result array
// to avoid an unnecessary write barrier storing the first result.
- Node* const first = a->SubString(context, string, start, end);
+ Node* const first = SubString(context, string, start, end);
Node* const result =
- a->AllocateRegExpResult(context, num_results, start, string);
- Node* const result_elements = a->LoadElements(result);
+ AllocateRegExpResult(context, num_results, start, string);
+ Node* const result_elements = LoadElements(result);
- a->StoreFixedArrayElement(result_elements, a->IntPtrConstant(0), first,
- SKIP_WRITE_BARRIER);
+ StoreFixedArrayElement(result_elements, 0, first, SKIP_WRITE_BARRIER);
- a->GotoIf(a->SmiEqual(num_results, a->SmiConstant(Smi::FromInt(1))), &out);
+ // If no captures exist we can skip named capture handling as well.
+ GotoIf(SmiEqual(num_results, SmiConstant(1)), &out);
// Store all remaining captures.
- Node* const limit = a->IntPtrAdd(
- a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), num_indices);
-
- Variable var_from_cursor(a, MachineType::PointerRepresentation());
- Variable var_to_cursor(a, MachineType::PointerRepresentation());
+ Node* const limit = IntPtrAdd(
+ IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), num_indices);
- var_from_cursor.Bind(
- a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 2));
- var_to_cursor.Bind(a->IntPtrConstant(1));
+ Variable var_from_cursor(
+ this, MachineType::PointerRepresentation(),
+ IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 2));
+ Variable var_to_cursor(this, MachineType::PointerRepresentation(),
+ IntPtrConstant(1));
Variable* vars[] = {&var_from_cursor, &var_to_cursor};
- Label loop(a, 2, vars);
+ Label loop(this, 2, vars);
- a->Goto(&loop);
- a->Bind(&loop);
+ Goto(&loop);
+ Bind(&loop);
{
Node* const from_cursor = var_from_cursor.value();
Node* const to_cursor = var_to_cursor.value();
- Node* const start = a->LoadFixedArrayElement(match_info, from_cursor);
+ Node* const start = LoadFixedArrayElement(match_info, from_cursor);
- Label next_iter(a);
- a->GotoIf(a->SmiEqual(start, a->SmiConstant(Smi::FromInt(-1))), &next_iter);
+ Label next_iter(this);
+ GotoIf(SmiEqual(start, SmiConstant(-1)), &next_iter);
- Node* const from_cursor_plus1 =
- a->IntPtrAdd(from_cursor, a->IntPtrConstant(1));
- Node* const end = a->LoadFixedArrayElement(match_info, from_cursor_plus1);
+ Node* const from_cursor_plus1 = IntPtrAdd(from_cursor, IntPtrConstant(1));
+ Node* const end = LoadFixedArrayElement(match_info, from_cursor_plus1);
- Node* const capture = a->SubString(context, string, start, end);
- a->StoreFixedArrayElement(result_elements, to_cursor, capture);
- a->Goto(&next_iter);
+ Node* const capture = SubString(context, string, start, end);
+ StoreFixedArrayElement(result_elements, to_cursor, capture);
+ Goto(&next_iter);
- a->Bind(&next_iter);
- var_from_cursor.Bind(a->IntPtrAdd(from_cursor, a->IntPtrConstant(2)));
- var_to_cursor.Bind(a->IntPtrAdd(to_cursor, a->IntPtrConstant(1)));
- a->Branch(a->UintPtrLessThan(var_from_cursor.value(), limit), &loop, &out);
+ Bind(&next_iter);
+ var_from_cursor.Bind(IntPtrAdd(from_cursor, IntPtrConstant(2)));
+ var_to_cursor.Bind(IntPtrAdd(to_cursor, IntPtrConstant(1)));
+ Branch(UintPtrLessThan(var_from_cursor.value(), limit), &loop,
+ &named_captures);
}
- a->Bind(&out);
+ Bind(&named_captures);
+ {
+ // We reach this point only if captures exist, implying that this is an
+ // IRREGEXP JSRegExp.
+
+ CSA_ASSERT(this, HasInstanceType(regexp, JS_REGEXP_TYPE));
+ CSA_ASSERT(this, SmiGreaterThan(num_results, SmiConstant(1)));
+
+ // Preparations for named capture properties. Exit early if the result does
+ // not have any named captures to minimize performance impact.
+
+ Node* const data = LoadObjectField(regexp, JSRegExp::kDataOffset);
+ CSA_ASSERT(this, SmiEqual(LoadFixedArrayElement(data, JSRegExp::kTagIndex),
+ SmiConstant(JSRegExp::IRREGEXP)));
+
+ // The names fixed array associates names at even indices with a capture
+ // index at odd indices.
+ Node* const names =
+ LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureNameMapIndex);
+ GotoIf(SmiEqual(names, SmiConstant(0)), &out);
+
+ // Allocate a new object to store the named capture properties.
+ // TODO(jgruber): Could be optimized by adding the object map to the heap
+ // root list.
+
+ Node* const native_context = LoadNativeContext(context);
+ Node* const map = LoadContextElement(
+ native_context, Context::SLOW_OBJECT_WITH_NULL_PROTOTYPE_MAP);
+ Node* const properties =
+ AllocateNameDictionary(NameDictionary::kInitialCapacity);
+
+ Node* const group_object = AllocateJSObjectFromMap(map, properties);
+
+ // Store it on the result as a 'group' property.
+
+ {
+ Node* const name = HeapConstant(isolate()->factory()->group_string());
+ CallRuntime(Runtime::kCreateDataProperty, context, result, name,
+ group_object);
+ }
+
+ // One or more named captures exist, add a property for each one.
+
+ CSA_ASSERT(this, HasInstanceType(names, FIXED_ARRAY_TYPE));
+ Node* const names_length = LoadAndUntagFixedArrayBaseLength(names);
+ CSA_ASSERT(this, IntPtrGreaterThan(names_length, IntPtrConstant(0)));
+
+ Variable var_i(this, MachineType::PointerRepresentation());
+ var_i.Bind(IntPtrConstant(0));
+
+ Variable* vars[] = {&var_i};
+ const int vars_count = sizeof(vars) / sizeof(vars[0]);
+ Label loop(this, vars_count, vars);
+
+ Goto(&loop);
+ Bind(&loop);
+ {
+ Node* const i = var_i.value();
+ Node* const i_plus_1 = IntPtrAdd(i, IntPtrConstant(1));
+ Node* const i_plus_2 = IntPtrAdd(i_plus_1, IntPtrConstant(1));
+
+ Node* const name = LoadFixedArrayElement(names, i);
+ Node* const index = LoadFixedArrayElement(names, i_plus_1);
+ Node* const capture =
+ LoadFixedArrayElement(result_elements, SmiUntag(index));
+
+ CallRuntime(Runtime::kCreateDataProperty, context, group_object, name,
+ capture);
+
+ var_i.Bind(i_plus_2);
+ Branch(IntPtrGreaterThanOrEqual(var_i.value(), names_length), &out,
+ &loop);
+ }
+ }
+
+ Bind(&out);
return result;
}
// ES#sec-regexp.prototype.exec
// RegExp.prototype.exec ( string )
-compiler::Node* RegExpPrototypeExecInternal(CodeStubAssembler* a,
- compiler::Node* context,
- compiler::Node* maybe_receiver,
- compiler::Node* maybe_string) {
- typedef CodeStubAssembler::Variable Variable;
- typedef CodeStubAssembler::Label Label;
- typedef compiler::Node Node;
-
- Isolate* const isolate = a->isolate();
-
- Node* const null = a->NullConstant();
- Node* const int_zero = a->IntPtrConstant(0);
- Node* const smi_zero = a->SmiConstant(Smi::kZero);
+// Implements the core of RegExp.prototype.exec but without actually
+// constructing the JSRegExpResult. Returns either null (if the RegExp did not
+// match) or a fixed array containing match indices as returned by
+// RegExpExecStub.
+Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult(
+ Node* const context, Node* const regexp, Node* const string,
+ Label* if_didnotmatch, const bool is_fastpath) {
+ Isolate* const isolate = this->isolate();
+
+ Node* const null = NullConstant();
+ Node* const int_zero = IntPtrConstant(0);
+ Node* const smi_zero = SmiConstant(Smi::kZero);
+
+ if (!is_fastpath) {
+ ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE,
+ "RegExp.prototype.exec");
+ }
- Variable var_result(a, MachineRepresentation::kTagged);
- Label out(a);
+ CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(string)));
+ CSA_ASSERT(this, HasInstanceType(regexp, JS_REGEXP_TYPE));
- // Ensure {maybe_receiver} is a JSRegExp.
- Node* const regexp_map = a->ThrowIfNotInstanceType(
- context, maybe_receiver, JS_REGEXP_TYPE, "RegExp.prototype.exec");
- Node* const regexp = maybe_receiver;
+ Variable var_result(this, MachineRepresentation::kTagged);
+ Label out(this);
- // Check whether the regexp instance is unmodified.
- Node* const native_context = a->LoadNativeContext(context);
- Node* const regexp_fun =
- a->LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
- Node* const initial_map =
- a->LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
- Node* const has_initialmap = a->WordEqual(regexp_map, initial_map);
+ // Load lastIndex.
+ Variable var_lastindex(this, MachineRepresentation::kTagged);
+ {
+ Node* const regexp_lastindex = LoadLastIndex(context, regexp, is_fastpath);
+ var_lastindex.Bind(regexp_lastindex);
+
+ if (is_fastpath) {
+ // ToLength on a positive smi is a nop and can be skipped.
+ CSA_ASSERT(this, TaggedIsPositiveSmi(regexp_lastindex));
+ } else {
+ // Omit ToLength if lastindex is a non-negative smi.
+ Label call_tolength(this, Label::kDeferred), next(this);
+ Branch(TaggedIsPositiveSmi(regexp_lastindex), &next, &call_tolength);
+
+ Bind(&call_tolength);
+ {
+ Callable tolength_callable = CodeFactory::ToLength(isolate);
+ var_lastindex.Bind(
+ CallStub(tolength_callable, context, regexp_lastindex));
+ Goto(&next);
+ }
- // Convert {maybe_string} to a string.
- Callable tostring_callable = CodeFactory::ToString(isolate);
- Node* const string = a->CallStub(tostring_callable, context, maybe_string);
- Node* const string_length = a->LoadStringLength(string);
+ Bind(&next);
+ }
+ }
// Check whether the regexp is global or sticky, which determines whether we
// update last index later on.
- Node* const flags = a->LoadObjectField(regexp, JSRegExp::kFlagsOffset);
- Node* const is_global_or_sticky =
- a->WordAnd(a->SmiUntag(flags),
- a->IntPtrConstant(JSRegExp::kGlobal | JSRegExp::kSticky));
+ Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset);
+ Node* const is_global_or_sticky = WordAnd(
+ SmiUntag(flags), IntPtrConstant(JSRegExp::kGlobal | JSRegExp::kSticky));
Node* const should_update_last_index =
- a->WordNotEqual(is_global_or_sticky, int_zero);
+ WordNotEqual(is_global_or_sticky, int_zero);
// Grab and possibly update last index.
- Label run_exec(a);
- Variable var_lastindex(a, MachineRepresentation::kTagged);
+ Label run_exec(this);
{
- Label if_doupdate(a), if_dontupdate(a);
- a->Branch(should_update_last_index, &if_doupdate, &if_dontupdate);
+ Label if_doupdate(this), if_dontupdate(this);
+ Branch(should_update_last_index, &if_doupdate, &if_dontupdate);
- a->Bind(&if_doupdate);
+ Bind(&if_doupdate);
{
- Node* const regexp_lastindex =
- LoadLastIndex(a, context, has_initialmap, regexp);
-
- Callable tolength_callable = CodeFactory::ToLength(isolate);
- Node* const lastindex =
- a->CallStub(tolength_callable, context, regexp_lastindex);
- var_lastindex.Bind(lastindex);
+ Node* const lastindex = var_lastindex.value();
- Label if_isoob(a, Label::kDeferred);
- a->GotoUnless(a->TaggedIsSmi(lastindex), &if_isoob);
- a->GotoUnless(a->SmiLessThanOrEqual(lastindex, string_length), &if_isoob);
- a->Goto(&run_exec);
+ Label if_isoob(this, Label::kDeferred);
+ GotoIfNot(TaggedIsSmi(lastindex), &if_isoob);
+ Node* const string_length = LoadStringLength(string);
+ GotoIfNot(SmiLessThanOrEqual(lastindex, string_length), &if_isoob);
+ Goto(&run_exec);
- a->Bind(&if_isoob);
+ Bind(&if_isoob);
{
- StoreLastIndex(a, context, has_initialmap, regexp, smi_zero);
+ StoreLastIndex(context, regexp, smi_zero, is_fastpath);
var_result.Bind(null);
- a->Goto(&out);
+ Goto(if_didnotmatch);
}
}
- a->Bind(&if_dontupdate);
+ Bind(&if_dontupdate);
{
var_lastindex.Bind(smi_zero);
- a->Goto(&run_exec);
+ Goto(&run_exec);
}
}
Node* match_indices;
- Label successful_match(a);
- a->Bind(&run_exec);
+ Label successful_match(this);
+ Bind(&run_exec);
{
// Get last match info from the context.
- Node* const last_match_info = a->LoadContextElement(
+ Node* const native_context = LoadNativeContext(context);
+ Node* const last_match_info = LoadContextElement(
native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
// Call the exec stub.
Callable exec_callable = CodeFactory::RegExpExec(isolate);
- match_indices = a->CallStub(exec_callable, context, regexp, string,
- var_lastindex.value(), last_match_info);
+ match_indices = CallStub(exec_callable, context, regexp, string,
+ var_lastindex.value(), last_match_info);
+ var_result.Bind(match_indices);
// {match_indices} is either null or the RegExpMatchInfo array.
// Return early if exec failed, possibly updating last index.
- a->GotoUnless(a->WordEqual(match_indices, null), &successful_match);
-
- Label return_null(a);
- a->GotoUnless(should_update_last_index, &return_null);
+ GotoIfNot(WordEqual(match_indices, null), &successful_match);
- StoreLastIndex(a, context, has_initialmap, regexp, smi_zero);
- a->Goto(&return_null);
+ GotoIfNot(should_update_last_index, if_didnotmatch);
- a->Bind(&return_null);
- var_result.Bind(null);
- a->Goto(&out);
+ StoreLastIndex(context, regexp, smi_zero, is_fastpath);
+ Goto(if_didnotmatch);
}
- Label construct_result(a);
- a->Bind(&successful_match);
+ Bind(&successful_match);
{
- a->GotoUnless(should_update_last_index, &construct_result);
+ GotoIfNot(should_update_last_index, &out);
// Update the new last index from {match_indices}.
- Node* const new_lastindex = a->LoadFixedArrayElement(
- match_indices,
- a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 1));
-
- StoreLastIndex(a, context, has_initialmap, regexp, new_lastindex);
- a->Goto(&construct_result);
+ Node* const new_lastindex = LoadFixedArrayElement(
+ match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1);
- a->Bind(&construct_result);
- {
- Node* result = ConstructNewResultFromMatchInfo(isolate, a, context,
- match_indices, string);
- var_result.Bind(result);
- a->Goto(&out);
- }
+ StoreLastIndex(context, regexp, new_lastindex, is_fastpath);
+ Goto(&out);
}
- a->Bind(&out);
+ Bind(&out);
return var_result.value();
}
-} // namespace
-
// ES#sec-regexp.prototype.exec
// RegExp.prototype.exec ( string )
-void Builtins::Generate_RegExpPrototypeExec(CodeStubAssembler* a) {
- typedef compiler::Node Node;
+Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBody(Node* const context,
+ Node* const regexp,
+ Node* const string,
+ const bool is_fastpath) {
+ Node* const null = NullConstant();
- Node* const maybe_receiver = a->Parameter(0);
- Node* const maybe_string = a->Parameter(1);
- Node* const context = a->Parameter(4);
+ Variable var_result(this, MachineRepresentation::kTagged);
- Node* const result =
- RegExpPrototypeExecInternal(a, context, maybe_receiver, maybe_string);
- a->Return(result);
-}
+ Label if_didnotmatch(this), out(this);
+ Node* const indices_or_null = RegExpPrototypeExecBodyWithoutResult(
+ context, regexp, string, &if_didnotmatch, is_fastpath);
-namespace {
+ // Successful match.
+ {
+ Node* const match_indices = indices_or_null;
+ Node* const result =
+ ConstructNewResultFromMatchInfo(context, regexp, match_indices, string);
+ var_result.Bind(result);
+ Goto(&out);
+ }
-compiler::Node* ThrowIfNotJSReceiver(CodeStubAssembler* a, Isolate* isolate,
- compiler::Node* context,
- compiler::Node* value,
- MessageTemplate::Template msg_template,
- char const* method_name) {
- typedef compiler::Node Node;
- typedef CodeStubAssembler::Label Label;
- typedef CodeStubAssembler::Variable Variable;
+ Bind(&if_didnotmatch);
+ {
+ var_result.Bind(null);
+ Goto(&out);
+ }
- Label out(a), throw_exception(a, Label::kDeferred);
- Variable var_value_map(a, MachineRepresentation::kTagged);
+ Bind(&out);
+ return var_result.value();
+}
- a->GotoIf(a->TaggedIsSmi(value), &throw_exception);
+Node* RegExpBuiltinsAssembler::ThrowIfNotJSReceiver(
+ Node* context, Node* maybe_receiver, MessageTemplate::Template msg_template,
+ char const* method_name) {
+ Label out(this), throw_exception(this, Label::kDeferred);
+ Variable var_value_map(this, MachineRepresentation::kTagged);
+
+ GotoIf(TaggedIsSmi(maybe_receiver), &throw_exception);
// Load the instance type of the {value}.
- var_value_map.Bind(a->LoadMap(value));
- Node* const value_instance_type =
- a->LoadMapInstanceType(var_value_map.value());
+ var_value_map.Bind(LoadMap(maybe_receiver));
+ Node* const value_instance_type = LoadMapInstanceType(var_value_map.value());
- a->Branch(a->IsJSReceiverInstanceType(value_instance_type), &out,
- &throw_exception);
+ Branch(IsJSReceiverInstanceType(value_instance_type), &out, &throw_exception);
// The {value} is not a compatible receiver for this method.
- a->Bind(&throw_exception);
+ Bind(&throw_exception);
{
- Node* const message_id = a->SmiConstant(Smi::FromInt(msg_template));
- Node* const method_name_str = a->HeapConstant(
- isolate->factory()->NewStringFromAsciiChecked(method_name, TENURED));
+ Node* const message_id = SmiConstant(Smi::FromInt(msg_template));
+ Node* const method_name_str = HeapConstant(
+ isolate()->factory()->NewStringFromAsciiChecked(method_name, TENURED));
- Callable callable = CodeFactory::ToString(isolate);
- Node* const value_str = a->CallStub(callable, context, value);
+ Callable callable = CodeFactory::ToString(isolate());
+ Node* const value_str = CallStub(callable, context, maybe_receiver);
- a->CallRuntime(Runtime::kThrowTypeError, context, message_id,
- method_name_str, value_str);
- var_value_map.Bind(a->UndefinedConstant());
- a->Goto(&out); // Never reached.
+ CallRuntime(Runtime::kThrowTypeError, context, message_id, method_name_str,
+ value_str);
+ Unreachable();
}
- a->Bind(&out);
+ Bind(&out);
return var_value_map.value();
}
-compiler::Node* IsInitialRegExpMap(CodeStubAssembler* a,
- compiler::Node* context,
- compiler::Node* map) {
- typedef compiler::Node Node;
+Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* object,
+ Node* map) {
+ Label out(this);
+ Variable var_result(this, MachineRepresentation::kWord32);
- Node* const native_context = a->LoadNativeContext(context);
+ Node* const native_context = LoadNativeContext(context);
Node* const regexp_fun =
- a->LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
+ LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
Node* const initial_map =
- a->LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
- Node* const has_initialmap = a->WordEqual(map, initial_map);
+ LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
+ Node* const has_initialmap = WordEqual(map, initial_map);
+
+ var_result.Bind(has_initialmap);
+ GotoIfNot(has_initialmap, &out);
+
+ // The smi check is required to omit ToLength(lastIndex) calls with possible
+ // user-code execution on the fast path.
+ Node* const last_index = FastLoadLastIndex(object);
+ var_result.Bind(TaggedIsPositiveSmi(last_index));
+ Goto(&out);
- return has_initialmap;
+ Bind(&out);
+ return var_result.value();
}
// RegExp fast path implementations rely on unmodified JSRegExp instances.
// We use a fairly coarse granularity for this and simply check whether both
-// the regexp itself is unmodified (i.e. its map has not changed) and its
-// prototype is unmodified.
-void BranchIfFastPath(CodeStubAssembler* a, compiler::Node* context,
- compiler::Node* map,
- CodeStubAssembler::Label* if_isunmodified,
- CodeStubAssembler::Label* if_ismodified) {
- typedef compiler::Node Node;
-
- Node* const native_context = a->LoadNativeContext(context);
+// the regexp itself is unmodified (i.e. its map has not changed), its
+// prototype is unmodified, and lastIndex is a non-negative smi.
+void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context,
+ Node* const object,
+ Node* const map,
+ Label* const if_isunmodified,
+ Label* const if_ismodified) {
+ CSA_ASSERT(this, WordEqual(LoadMap(object), map));
+
+ // TODO(ishell): Update this check once map changes for constant field
+ // tracking are landing.
+
+ Node* const native_context = LoadNativeContext(context);
Node* const regexp_fun =
- a->LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
+ LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
Node* const initial_map =
- a->LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
- Node* const has_initialmap = a->WordEqual(map, initial_map);
+ LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
+ Node* const has_initialmap = WordEqual(map, initial_map);
- a->GotoUnless(has_initialmap, if_ismodified);
+ GotoIfNot(has_initialmap, if_ismodified);
- Node* const initial_proto_initial_map = a->LoadContextElement(
- native_context, Context::REGEXP_PROTOTYPE_MAP_INDEX);
- Node* const proto_map = a->LoadMap(a->LoadMapPrototype(map));
+ Node* const initial_proto_initial_map =
+ LoadContextElement(native_context, Context::REGEXP_PROTOTYPE_MAP_INDEX);
+ Node* const proto_map = LoadMap(LoadMapPrototype(map));
Node* const proto_has_initialmap =
- a->WordEqual(proto_map, initial_proto_initial_map);
+ WordEqual(proto_map, initial_proto_initial_map);
- // TODO(ishell): Update this check once map changes for constant field
- // tracking are landing.
+ GotoIfNot(proto_has_initialmap, if_ismodified);
- a->Branch(proto_has_initialmap, if_isunmodified, if_ismodified);
+ // The smi check is required to omit ToLength(lastIndex) calls with possible
+ // user-code execution on the fast path.
+ Node* const last_index = FastLoadLastIndex(object);
+ Branch(TaggedIsPositiveSmi(last_index), if_isunmodified, if_ismodified);
}
-} // namespace
+Node* RegExpBuiltinsAssembler::IsFastRegExpMap(Node* const context,
+ Node* const object,
+ Node* const map) {
+ Label yup(this), nope(this), out(this);
+ Variable var_result(this, MachineRepresentation::kWord32);
-void Builtins::Generate_RegExpPrototypeFlagsGetter(CodeStubAssembler* a) {
- typedef CodeStubAssembler::Variable Variable;
- typedef CodeStubAssembler::Label Label;
- typedef compiler::Node Node;
+ BranchIfFastRegExp(context, object, map, &yup, &nope);
- Node* const receiver = a->Parameter(0);
- Node* const context = a->Parameter(3);
+ Bind(&yup);
+ var_result.Bind(Int32Constant(1));
+ Goto(&out);
- Isolate* isolate = a->isolate();
- Node* const int_zero = a->IntPtrConstant(0);
- Node* const int_one = a->IntPtrConstant(1);
+ Bind(&nope);
+ var_result.Bind(Int32Constant(0));
+ Goto(&out);
- Node* const map = ThrowIfNotJSReceiver(a, isolate, context, receiver,
- MessageTemplate::kRegExpNonObject,
- "RegExp.prototype.flags");
+ Bind(&out);
+ return var_result.value();
+}
- Variable var_length(a, MachineType::PointerRepresentation());
- Variable var_flags(a, MachineType::PointerRepresentation());
+void RegExpBuiltinsAssembler::BranchIfFastRegExpResult(Node* context, Node* map,
+ Label* if_isunmodified,
+ Label* if_ismodified) {
+ Node* const native_context = LoadNativeContext(context);
+ Node* const initial_regexp_result_map =
+ LoadContextElement(native_context, Context::REGEXP_RESULT_MAP_INDEX);
- // First, count the number of characters we will need and check which flags
- // are set.
+ Branch(WordEqual(map, initial_regexp_result_map), if_isunmodified,
+ if_ismodified);
+}
+
+// ES#sec-regexp.prototype.exec
+// RegExp.prototype.exec ( string )
+TF_BUILTIN(RegExpPrototypeExec, RegExpBuiltinsAssembler) {
+ Node* const maybe_receiver = Parameter(0);
+ Node* const maybe_string = Parameter(1);
+ Node* const context = Parameter(4);
+
+ // Ensure {maybe_receiver} is a JSRegExp.
+ ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE,
+ "RegExp.prototype.exec");
+ Node* const receiver = maybe_receiver;
- var_length.Bind(int_zero);
+ // Convert {maybe_string} to a String.
+ Node* const string = ToString(context, maybe_string);
+
+ Label if_isfastpath(this), if_isslowpath(this);
+ Branch(IsInitialRegExpMap(context, receiver, LoadMap(receiver)),
+ &if_isfastpath, &if_isslowpath);
- Label if_isunmodifiedjsregexp(a),
- if_isnotunmodifiedjsregexp(a, Label::kDeferred);
- a->Branch(IsInitialRegExpMap(a, context, map), &if_isunmodifiedjsregexp,
- &if_isnotunmodifiedjsregexp);
+ Bind(&if_isfastpath);
+ {
+ Node* const result =
+ RegExpPrototypeExecBody(context, receiver, string, true);
+ Return(result);
+ }
- Label construct_string(a);
- a->Bind(&if_isunmodifiedjsregexp);
+ Bind(&if_isslowpath);
{
+ Node* const result =
+ RegExpPrototypeExecBody(context, receiver, string, false);
+ Return(result);
+ }
+}
+
+Node* RegExpBuiltinsAssembler::FlagsGetter(Node* const context,
+ Node* const regexp,
+ bool is_fastpath) {
+ Isolate* isolate = this->isolate();
+
+ Node* const int_zero = IntPtrConstant(0);
+ Node* const int_one = IntPtrConstant(1);
+ Variable var_length(this, MachineType::PointerRepresentation(), int_zero);
+ Variable var_flags(this, MachineType::PointerRepresentation());
+
+ // First, count the number of characters we will need and check which flags
+ // are set.
+
+ if (is_fastpath) {
// Refer to JSRegExp's flag property on the fast-path.
- Node* const flags_smi =
- a->LoadObjectField(receiver, JSRegExp::kFlagsOffset);
- Node* const flags_intptr = a->SmiUntag(flags_smi);
+ Node* const flags_smi = LoadObjectField(regexp, JSRegExp::kFlagsOffset);
+ Node* const flags_intptr = SmiUntag(flags_smi);
var_flags.Bind(flags_intptr);
- Label label_global(a), label_ignorecase(a), label_multiline(a),
- label_unicode(a), label_sticky(a);
-
-#define CASE_FOR_FLAG(FLAG, LABEL, NEXT_LABEL) \
- do { \
- a->Bind(&LABEL); \
- Node* const mask = a->IntPtrConstant(FLAG); \
- a->GotoIf(a->WordEqual(a->WordAnd(flags_intptr, mask), int_zero), \
- &NEXT_LABEL); \
- var_length.Bind(a->IntPtrAdd(var_length.value(), int_one)); \
- a->Goto(&NEXT_LABEL); \
+#define CASE_FOR_FLAG(FLAG) \
+ do { \
+ Label next(this); \
+ GotoIfNot(IsSetWord(flags_intptr, FLAG), &next); \
+ var_length.Bind(IntPtrAdd(var_length.value(), int_one)); \
+ Goto(&next); \
+ Bind(&next); \
} while (false)
- a->Goto(&label_global);
- CASE_FOR_FLAG(JSRegExp::kGlobal, label_global, label_ignorecase);
- CASE_FOR_FLAG(JSRegExp::kIgnoreCase, label_ignorecase, label_multiline);
- CASE_FOR_FLAG(JSRegExp::kMultiline, label_multiline, label_unicode);
- CASE_FOR_FLAG(JSRegExp::kUnicode, label_unicode, label_sticky);
- CASE_FOR_FLAG(JSRegExp::kSticky, label_sticky, construct_string);
+ CASE_FOR_FLAG(JSRegExp::kGlobal);
+ CASE_FOR_FLAG(JSRegExp::kIgnoreCase);
+ CASE_FOR_FLAG(JSRegExp::kMultiline);
+ CASE_FOR_FLAG(JSRegExp::kUnicode);
+ CASE_FOR_FLAG(JSRegExp::kSticky);
#undef CASE_FOR_FLAG
- }
+ } else {
+ DCHECK(!is_fastpath);
- a->Bind(&if_isnotunmodifiedjsregexp);
- {
// Fall back to GetProperty stub on the slow-path.
var_flags.Bind(int_zero);
- Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
- Label label_global(a), label_ignorecase(a), label_multiline(a),
- label_unicode(a), label_sticky(a);
+ Callable getproperty_callable = CodeFactory::GetProperty(isolate);
-#define CASE_FOR_FLAG(NAME, FLAG, LABEL, NEXT_LABEL) \
+#define CASE_FOR_FLAG(NAME, FLAG) \
do { \
- a->Bind(&LABEL); \
+ Label next(this); \
Node* const name = \
- a->HeapConstant(isolate->factory()->NewStringFromAsciiChecked(NAME)); \
- Node* const flag = \
- a->CallStub(getproperty_callable, context, receiver, name); \
- Label if_isflagset(a); \
- a->BranchIfToBooleanIsTrue(flag, &if_isflagset, &NEXT_LABEL); \
- a->Bind(&if_isflagset); \
- var_length.Bind(a->IntPtrAdd(var_length.value(), int_one)); \
- var_flags.Bind(a->WordOr(var_flags.value(), a->IntPtrConstant(FLAG))); \
- a->Goto(&NEXT_LABEL); \
+ HeapConstant(isolate->factory()->InternalizeUtf8String(NAME)); \
+ Node* const flag = CallStub(getproperty_callable, context, regexp, name); \
+ Label if_isflagset(this); \
+ BranchIfToBooleanIsTrue(flag, &if_isflagset, &next); \
+ Bind(&if_isflagset); \
+ var_length.Bind(IntPtrAdd(var_length.value(), int_one)); \
+ var_flags.Bind(WordOr(var_flags.value(), IntPtrConstant(FLAG))); \
+ Goto(&next); \
+ Bind(&next); \
} while (false)
- a->Goto(&label_global);
- CASE_FOR_FLAG("global", JSRegExp::kGlobal, label_global, label_ignorecase);
- CASE_FOR_FLAG("ignoreCase", JSRegExp::kIgnoreCase, label_ignorecase,
- label_multiline);
- CASE_FOR_FLAG("multiline", JSRegExp::kMultiline, label_multiline,
- label_unicode);
- CASE_FOR_FLAG("unicode", JSRegExp::kUnicode, label_unicode, label_sticky);
- CASE_FOR_FLAG("sticky", JSRegExp::kSticky, label_sticky, construct_string);
+ CASE_FOR_FLAG("global", JSRegExp::kGlobal);
+ CASE_FOR_FLAG("ignoreCase", JSRegExp::kIgnoreCase);
+ CASE_FOR_FLAG("multiline", JSRegExp::kMultiline);
+ CASE_FOR_FLAG("unicode", JSRegExp::kUnicode);
+ CASE_FOR_FLAG("sticky", JSRegExp::kSticky);
#undef CASE_FOR_FLAG
}
// Allocate a string of the required length and fill it with the corresponding
// char for each set flag.
- a->Bind(&construct_string);
{
- Node* const result =
- a->AllocateSeqOneByteString(context, var_length.value());
+ Node* const result = AllocateSeqOneByteString(context, var_length.value());
Node* const flags_intptr = var_flags.value();
- Variable var_offset(a, MachineType::PointerRepresentation());
- var_offset.Bind(
- a->IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag));
-
- Label label_global(a), label_ignorecase(a), label_multiline(a),
- label_unicode(a), label_sticky(a), out(a);
-
-#define CASE_FOR_FLAG(FLAG, CHAR, LABEL, NEXT_LABEL) \
- do { \
- a->Bind(&LABEL); \
- Node* const mask = a->IntPtrConstant(FLAG); \
- a->GotoIf(a->WordEqual(a->WordAnd(flags_intptr, mask), int_zero), \
- &NEXT_LABEL); \
- Node* const value = a->IntPtrConstant(CHAR); \
- a->StoreNoWriteBarrier(MachineRepresentation::kWord8, result, \
- var_offset.value(), value); \
- var_offset.Bind(a->IntPtrAdd(var_offset.value(), int_one)); \
- a->Goto(&NEXT_LABEL); \
+ Variable var_offset(
+ this, MachineType::PointerRepresentation(),
+ IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag));
+
+#define CASE_FOR_FLAG(FLAG, CHAR) \
+ do { \
+ Label next(this); \
+ GotoIfNot(IsSetWord(flags_intptr, FLAG), &next); \
+ Node* const value = Int32Constant(CHAR); \
+ StoreNoWriteBarrier(MachineRepresentation::kWord8, result, \
+ var_offset.value(), value); \
+ var_offset.Bind(IntPtrAdd(var_offset.value(), int_one)); \
+ Goto(&next); \
+ Bind(&next); \
} while (false)
- a->Goto(&label_global);
- CASE_FOR_FLAG(JSRegExp::kGlobal, 'g', label_global, label_ignorecase);
- CASE_FOR_FLAG(JSRegExp::kIgnoreCase, 'i', label_ignorecase,
- label_multiline);
- CASE_FOR_FLAG(JSRegExp::kMultiline, 'm', label_multiline, label_unicode);
- CASE_FOR_FLAG(JSRegExp::kUnicode, 'u', label_unicode, label_sticky);
- CASE_FOR_FLAG(JSRegExp::kSticky, 'y', label_sticky, out);
+ CASE_FOR_FLAG(JSRegExp::kGlobal, 'g');
+ CASE_FOR_FLAG(JSRegExp::kIgnoreCase, 'i');
+ CASE_FOR_FLAG(JSRegExp::kMultiline, 'm');
+ CASE_FOR_FLAG(JSRegExp::kUnicode, 'u');
+ CASE_FOR_FLAG(JSRegExp::kSticky, 'y');
#undef CASE_FOR_FLAG
- a->Bind(&out);
- a->Return(result);
+ return result;
}
}
-// ES6 21.2.5.10.
-BUILTIN(RegExpPrototypeSourceGetter) {
- HandleScope scope(isolate);
+// ES#sec-isregexp IsRegExp ( argument )
+Node* RegExpBuiltinsAssembler::IsRegExp(Node* const context,
+ Node* const maybe_receiver) {
+ Label out(this), if_isregexp(this);
+
+ Variable var_result(this, MachineRepresentation::kWord32, Int32Constant(0));
+
+ GotoIf(TaggedIsSmi(maybe_receiver), &out);
+ GotoIfNot(IsJSReceiver(maybe_receiver), &out);
+
+ Node* const receiver = maybe_receiver;
+
+ // Check @@match.
+ {
+ Callable getproperty_callable = CodeFactory::GetProperty(isolate());
+ Node* const name = HeapConstant(isolate()->factory()->match_symbol());
+ Node* const value = CallStub(getproperty_callable, context, receiver, name);
+
+ Label match_isundefined(this), match_isnotundefined(this);
+ Branch(IsUndefined(value), &match_isundefined, &match_isnotundefined);
+
+ Bind(&match_isundefined);
+ Branch(HasInstanceType(receiver, JS_REGEXP_TYPE), &if_isregexp, &out);
+
+ Bind(&match_isnotundefined);
+ BranchIfToBooleanIsTrue(value, &if_isregexp, &out);
+ }
+
+ Bind(&if_isregexp);
+ var_result.Bind(Int32Constant(1));
+ Goto(&out);
+
+ Bind(&out);
+ return var_result.value();
+}
+
+// ES#sec-regexpinitialize
+// Runtime Semantics: RegExpInitialize ( obj, pattern, flags )
+Node* RegExpBuiltinsAssembler::RegExpInitialize(Node* const context,
+ Node* const regexp,
+ Node* const maybe_pattern,
+ Node* const maybe_flags) {
+ // Normalize pattern.
+ Node* const pattern =
+ Select(IsUndefined(maybe_pattern), [=] { return EmptyStringConstant(); },
+ [=] { return ToString(context, maybe_pattern); },
+ MachineRepresentation::kTagged);
+
+ // Normalize flags.
+ Node* const flags =
+ Select(IsUndefined(maybe_flags), [=] { return EmptyStringConstant(); },
+ [=] { return ToString(context, maybe_flags); },
+ MachineRepresentation::kTagged);
+
+ // Initialize.
+
+ return CallRuntime(Runtime::kRegExpInitializeAndCompile, context, regexp,
+ pattern, flags);
+}
+
+TF_BUILTIN(RegExpPrototypeFlagsGetter, RegExpBuiltinsAssembler) {
+ Node* const maybe_receiver = Parameter(0);
+ Node* const context = Parameter(3);
+
+ Node* const map = ThrowIfNotJSReceiver(context, maybe_receiver,
+ MessageTemplate::kRegExpNonObject,
+ "RegExp.prototype.flags");
+ Node* const receiver = maybe_receiver;
+
+ Label if_isfastpath(this), if_isslowpath(this, Label::kDeferred);
+ Branch(IsInitialRegExpMap(context, receiver, map), &if_isfastpath,
+ &if_isslowpath);
+
+ Bind(&if_isfastpath);
+ Return(FlagsGetter(context, receiver, true));
+
+ Bind(&if_isslowpath);
+ Return(FlagsGetter(context, receiver, false));
+}
+
+// ES#sec-regexp-pattern-flags
+// RegExp ( pattern, flags )
+TF_BUILTIN(RegExpConstructor, RegExpBuiltinsAssembler) {
+ Node* const pattern = Parameter(1);
+ Node* const flags = Parameter(2);
+ Node* const new_target = Parameter(3);
+ Node* const context = Parameter(5);
+
+ Isolate* isolate = this->isolate();
+
+ Variable var_flags(this, MachineRepresentation::kTagged, flags);
+ Variable var_pattern(this, MachineRepresentation::kTagged, pattern);
+ Variable var_new_target(this, MachineRepresentation::kTagged, new_target);
+
+ Node* const native_context = LoadNativeContext(context);
+ Node* const regexp_function =
+ LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
+
+ Node* const pattern_is_regexp = IsRegExp(context, pattern);
+
+ {
+ Label next(this);
+
+ GotoIfNot(IsUndefined(new_target), &next);
+ var_new_target.Bind(regexp_function);
+
+ GotoIfNot(pattern_is_regexp, &next);
+ GotoIfNot(IsUndefined(flags), &next);
+
+ Callable getproperty_callable = CodeFactory::GetProperty(isolate);
+ Node* const name = HeapConstant(isolate->factory()->constructor_string());
+ Node* const value = CallStub(getproperty_callable, context, pattern, name);
+
+ GotoIfNot(WordEqual(value, regexp_function), &next);
+ Return(pattern);
+
+ Bind(&next);
+ }
+
+ {
+ Label next(this), if_patternisfastregexp(this),
+ if_patternisslowregexp(this);
+ GotoIf(TaggedIsSmi(pattern), &next);
+
+ GotoIf(HasInstanceType(pattern, JS_REGEXP_TYPE), &if_patternisfastregexp);
+
+ Branch(pattern_is_regexp, &if_patternisslowregexp, &next);
+
+ Bind(&if_patternisfastregexp);
+ {
+ Node* const source = LoadObjectField(pattern, JSRegExp::kSourceOffset);
+ var_pattern.Bind(source);
+
+ {
+ Label inner_next(this);
+ GotoIfNot(IsUndefined(flags), &inner_next);
+
+ Node* const value = FlagsGetter(context, pattern, true);
+ var_flags.Bind(value);
+ Goto(&inner_next);
+
+ Bind(&inner_next);
+ }
- Handle<Object> recv = args.receiver();
- if (!recv->IsJSRegExp()) {
- Handle<JSFunction> regexp_fun = isolate->regexp_function();
- if (*recv == regexp_fun->prototype()) {
- isolate->CountUsage(v8::Isolate::kRegExpPrototypeSourceGetter);
- return *isolate->factory()->NewStringFromAsciiChecked("(?:)");
+ Goto(&next);
}
- THROW_NEW_ERROR_RETURN_FAILURE(
- isolate, NewTypeError(MessageTemplate::kRegExpNonRegExp,
- isolate->factory()->NewStringFromAsciiChecked(
- "RegExp.prototype.source")));
+
+ Bind(&if_patternisslowregexp);
+ {
+ Callable getproperty_callable = CodeFactory::GetProperty(isolate);
+
+ {
+ Node* const name = HeapConstant(isolate->factory()->source_string());
+ Node* const value =
+ CallStub(getproperty_callable, context, pattern, name);
+ var_pattern.Bind(value);
+ }
+
+ {
+ Label inner_next(this);
+ GotoIfNot(IsUndefined(flags), &inner_next);
+
+ Node* const name = HeapConstant(isolate->factory()->flags_string());
+ Node* const value =
+ CallStub(getproperty_callable, context, pattern, name);
+ var_flags.Bind(value);
+ Goto(&inner_next);
+
+ Bind(&inner_next);
+ }
+
+ Goto(&next);
+ }
+
+ Bind(&next);
+ }
+
+ // Allocate.
+
+ Variable var_regexp(this, MachineRepresentation::kTagged);
+ {
+ Label allocate_jsregexp(this), allocate_generic(this, Label::kDeferred),
+ next(this);
+ Branch(WordEqual(var_new_target.value(), regexp_function),
+ &allocate_jsregexp, &allocate_generic);
+
+ Bind(&allocate_jsregexp);
+ {
+ Node* const initial_map = LoadObjectField(
+ regexp_function, JSFunction::kPrototypeOrInitialMapOffset);
+ Node* const regexp = AllocateJSObjectFromMap(initial_map);
+ var_regexp.Bind(regexp);
+ Goto(&next);
+ }
+
+ Bind(&allocate_generic);
+ {
+ ConstructorBuiltinsAssembler constructor_assembler(this->state());
+ Node* const regexp = constructor_assembler.EmitFastNewObject(
+ context, regexp_function, var_new_target.value());
+ var_regexp.Bind(regexp);
+ Goto(&next);
+ }
+
+ Bind(&next);
+ }
+
+ Node* const result = RegExpInitialize(context, var_regexp.value(),
+ var_pattern.value(), var_flags.value());
+ Return(result);
+}
+
+// ES#sec-regexp.prototype.compile
+// RegExp.prototype.compile ( pattern, flags )
+TF_BUILTIN(RegExpPrototypeCompile, RegExpBuiltinsAssembler) {
+ Node* const maybe_receiver = Parameter(0);
+ Node* const maybe_pattern = Parameter(1);
+ Node* const maybe_flags = Parameter(2);
+ Node* const context = Parameter(5);
+
+ ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE,
+ "RegExp.prototype.compile");
+ Node* const receiver = maybe_receiver;
+
+ Variable var_flags(this, MachineRepresentation::kTagged, maybe_flags);
+ Variable var_pattern(this, MachineRepresentation::kTagged, maybe_pattern);
+
+ // Handle a JSRegExp pattern.
+ {
+ Label next(this);
+
+ GotoIf(TaggedIsSmi(maybe_pattern), &next);
+ GotoIfNot(HasInstanceType(maybe_pattern, JS_REGEXP_TYPE), &next);
+
+ Node* const pattern = maybe_pattern;
+
+ // {maybe_flags} must be undefined in this case, otherwise throw.
+ {
+ Label next(this);
+ GotoIf(IsUndefined(maybe_flags), &next);
+
+ Node* const message_id = SmiConstant(MessageTemplate::kRegExpFlags);
+ TailCallRuntime(Runtime::kThrowTypeError, context, message_id);
+
+ Bind(&next);
+ }
+
+ Node* const new_flags = FlagsGetter(context, pattern, true);
+ Node* const new_pattern = LoadObjectField(pattern, JSRegExp::kSourceOffset);
+
+ var_flags.Bind(new_flags);
+ var_pattern.Bind(new_pattern);
+
+ Goto(&next);
+ Bind(&next);
}
- Handle<JSRegExp> regexp = Handle<JSRegExp>::cast(recv);
- return regexp->source();
+ Node* const result = RegExpInitialize(context, receiver, var_pattern.value(),
+ var_flags.value());
+ Return(result);
+}
+
+// ES6 21.2.5.10.
+TF_BUILTIN(RegExpPrototypeSourceGetter, RegExpBuiltinsAssembler) {
+ Node* const receiver = Parameter(0);
+ Node* const context = Parameter(3);
+
+ // Check whether we have an unmodified regexp instance.
+ Label if_isjsregexp(this), if_isnotjsregexp(this, Label::kDeferred);
+
+ GotoIf(TaggedIsSmi(receiver), &if_isnotjsregexp);
+ Branch(HasInstanceType(receiver, JS_REGEXP_TYPE), &if_isjsregexp,
+ &if_isnotjsregexp);
+
+ Bind(&if_isjsregexp);
+ {
+ Node* const source = LoadObjectField(receiver, JSRegExp::kSourceOffset);
+ Return(source);
+ }
+
+ Bind(&if_isnotjsregexp);
+ {
+ Isolate* isolate = this->isolate();
+ Node* const native_context = LoadNativeContext(context);
+ Node* const regexp_fun =
+ LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
+ Node* const initial_map =
+ LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
+ Node* const initial_prototype = LoadMapPrototype(initial_map);
+
+ Label if_isprototype(this), if_isnotprototype(this);
+ Branch(WordEqual(receiver, initial_prototype), &if_isprototype,
+ &if_isnotprototype);
+
+ Bind(&if_isprototype);
+ {
+ const int counter = v8::Isolate::kRegExpPrototypeSourceGetter;
+ Node* const counter_smi = SmiConstant(counter);
+ CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi);
+
+ Node* const result =
+ HeapConstant(isolate->factory()->NewStringFromAsciiChecked("(?:)"));
+ Return(result);
+ }
+
+ Bind(&if_isnotprototype);
+ {
+ Node* const message_id =
+ SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonRegExp));
+ Node* const method_name_str =
+ HeapConstant(isolate->factory()->NewStringFromAsciiChecked(
+ "RegExp.prototype.source"));
+ TailCallRuntime(Runtime::kThrowTypeError, context, message_id,
+ method_name_str);
+ }
+ }
}
BUILTIN(RegExpPrototypeToString) {
@@ -781,126 +994,166 @@ BUILTIN(RegExpPrototypeToString) {
RETURN_RESULT_OR_FAILURE(isolate, builder.Finish());
}
-// ES6 21.2.4.2.
-BUILTIN(RegExpPrototypeSpeciesGetter) {
- HandleScope scope(isolate);
- return *args.receiver();
+// Fast-path implementation for flag checks on an unmodified JSRegExp instance.
+Node* RegExpBuiltinsAssembler::FastFlagGetter(Node* const regexp,
+ JSRegExp::Flag flag) {
+ Node* const smi_zero = SmiConstant(Smi::kZero);
+ Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset);
+ Node* const mask = SmiConstant(Smi::FromInt(flag));
+ Node* const is_flag_set = WordNotEqual(SmiAnd(flags, mask), smi_zero);
+
+ return is_flag_set;
}
-namespace {
+// Load through the GetProperty stub.
+Node* RegExpBuiltinsAssembler::SlowFlagGetter(Node* const context,
+ Node* const regexp,
+ JSRegExp::Flag flag) {
+ Factory* factory = isolate()->factory();
-// Fast-path implementation for flag checks on an unmodified JSRegExp instance.
-compiler::Node* FastFlagGetter(CodeStubAssembler* a,
- compiler::Node* const regexp,
- JSRegExp::Flag flag) {
- typedef compiler::Node Node;
+ Label out(this);
+ Variable var_result(this, MachineRepresentation::kWord32);
- Node* const smi_zero = a->SmiConstant(Smi::kZero);
- Node* const flags = a->LoadObjectField(regexp, JSRegExp::kFlagsOffset);
- Node* const mask = a->SmiConstant(Smi::FromInt(flag));
- Node* const is_flag_set = a->WordNotEqual(a->WordAnd(flags, mask), smi_zero);
+ Node* name;
- return is_flag_set;
+ switch (flag) {
+ case JSRegExp::kGlobal:
+ name = HeapConstant(factory->global_string());
+ break;
+ case JSRegExp::kIgnoreCase:
+ name = HeapConstant(factory->ignoreCase_string());
+ break;
+ case JSRegExp::kMultiline:
+ name = HeapConstant(factory->multiline_string());
+ break;
+ case JSRegExp::kSticky:
+ name = HeapConstant(factory->sticky_string());
+ break;
+ case JSRegExp::kUnicode:
+ name = HeapConstant(factory->unicode_string());
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ Callable getproperty_callable = CodeFactory::GetProperty(isolate());
+ Node* const value = CallStub(getproperty_callable, context, regexp, name);
+
+ Label if_true(this), if_false(this);
+ BranchIfToBooleanIsTrue(value, &if_true, &if_false);
+
+ Bind(&if_true);
+ {
+ var_result.Bind(Int32Constant(1));
+ Goto(&out);
+ }
+
+ Bind(&if_false);
+ {
+ var_result.Bind(Int32Constant(0));
+ Goto(&out);
+ }
+
+ Bind(&out);
+ return var_result.value();
}
-void Generate_FlagGetter(CodeStubAssembler* a, JSRegExp::Flag flag,
- v8::Isolate::UseCounterFeature counter,
- const char* method_name) {
- typedef CodeStubAssembler::Label Label;
- typedef compiler::Node Node;
+Node* RegExpBuiltinsAssembler::FlagGetter(Node* const context,
+ Node* const regexp,
+ JSRegExp::Flag flag,
+ bool is_fastpath) {
+ return is_fastpath ? FastFlagGetter(regexp, flag)
+ : SlowFlagGetter(context, regexp, flag);
+}
- Node* const receiver = a->Parameter(0);
- Node* const context = a->Parameter(3);
+void RegExpBuiltinsAssembler::FlagGetter(JSRegExp::Flag flag,
+ v8::Isolate::UseCounterFeature counter,
+ const char* method_name) {
+ Node* const receiver = Parameter(0);
+ Node* const context = Parameter(3);
- Isolate* isolate = a->isolate();
+ Isolate* isolate = this->isolate();
// Check whether we have an unmodified regexp instance.
- Label if_isunmodifiedjsregexp(a),
- if_isnotunmodifiedjsregexp(a, Label::kDeferred);
+ Label if_isunmodifiedjsregexp(this),
+ if_isnotunmodifiedjsregexp(this, Label::kDeferred);
- a->GotoIf(a->TaggedIsSmi(receiver), &if_isnotunmodifiedjsregexp);
+ GotoIf(TaggedIsSmi(receiver), &if_isnotunmodifiedjsregexp);
- Node* const receiver_map = a->LoadMap(receiver);
- Node* const instance_type = a->LoadMapInstanceType(receiver_map);
+ Node* const receiver_map = LoadMap(receiver);
+ Node* const instance_type = LoadMapInstanceType(receiver_map);
- a->Branch(a->Word32Equal(instance_type, a->Int32Constant(JS_REGEXP_TYPE)),
- &if_isunmodifiedjsregexp, &if_isnotunmodifiedjsregexp);
+ Branch(Word32Equal(instance_type, Int32Constant(JS_REGEXP_TYPE)),
+ &if_isunmodifiedjsregexp, &if_isnotunmodifiedjsregexp);
- a->Bind(&if_isunmodifiedjsregexp);
+ Bind(&if_isunmodifiedjsregexp);
{
// Refer to JSRegExp's flag property on the fast-path.
- Node* const is_flag_set = FastFlagGetter(a, receiver, flag);
- a->Return(a->Select(is_flag_set, a->TrueConstant(), a->FalseConstant()));
+ Node* const is_flag_set = FastFlagGetter(receiver, flag);
+ Return(SelectBooleanConstant(is_flag_set));
}
- a->Bind(&if_isnotunmodifiedjsregexp);
+ Bind(&if_isnotunmodifiedjsregexp);
{
- Node* const native_context = a->LoadNativeContext(context);
+ Node* const native_context = LoadNativeContext(context);
Node* const regexp_fun =
- a->LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
- Node* const initial_map = a->LoadObjectField(
- regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
- Node* const initial_prototype = a->LoadMapPrototype(initial_map);
+ LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
+ Node* const initial_map =
+ LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
+ Node* const initial_prototype = LoadMapPrototype(initial_map);
- Label if_isprototype(a), if_isnotprototype(a);
- a->Branch(a->WordEqual(receiver, initial_prototype), &if_isprototype,
- &if_isnotprototype);
+ Label if_isprototype(this), if_isnotprototype(this);
+ Branch(WordEqual(receiver, initial_prototype), &if_isprototype,
+ &if_isnotprototype);
- a->Bind(&if_isprototype);
+ Bind(&if_isprototype);
{
- Node* const counter_smi = a->SmiConstant(Smi::FromInt(counter));
- a->CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi);
- a->Return(a->UndefinedConstant());
+ Node* const counter_smi = SmiConstant(Smi::FromInt(counter));
+ CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi);
+ Return(UndefinedConstant());
}
- a->Bind(&if_isnotprototype);
+ Bind(&if_isnotprototype);
{
Node* const message_id =
- a->SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonRegExp));
- Node* const method_name_str = a->HeapConstant(
+ SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonRegExp));
+ Node* const method_name_str = HeapConstant(
isolate->factory()->NewStringFromAsciiChecked(method_name));
- a->CallRuntime(Runtime::kThrowTypeError, context, message_id,
- method_name_str);
- a->Return(a->UndefinedConstant()); // Never reached.
+ CallRuntime(Runtime::kThrowTypeError, context, message_id,
+ method_name_str);
+ Unreachable();
}
}
}
-} // namespace
-
// ES6 21.2.5.4.
-void Builtins::Generate_RegExpPrototypeGlobalGetter(CodeStubAssembler* a) {
- Generate_FlagGetter(a, JSRegExp::kGlobal,
- v8::Isolate::kRegExpPrototypeOldFlagGetter,
- "RegExp.prototype.global");
+TF_BUILTIN(RegExpPrototypeGlobalGetter, RegExpBuiltinsAssembler) {
+ FlagGetter(JSRegExp::kGlobal, v8::Isolate::kRegExpPrototypeOldFlagGetter,
+ "RegExp.prototype.global");
}
// ES6 21.2.5.5.
-void Builtins::Generate_RegExpPrototypeIgnoreCaseGetter(CodeStubAssembler* a) {
- Generate_FlagGetter(a, JSRegExp::kIgnoreCase,
- v8::Isolate::kRegExpPrototypeOldFlagGetter,
- "RegExp.prototype.ignoreCase");
+TF_BUILTIN(RegExpPrototypeIgnoreCaseGetter, RegExpBuiltinsAssembler) {
+ FlagGetter(JSRegExp::kIgnoreCase, v8::Isolate::kRegExpPrototypeOldFlagGetter,
+ "RegExp.prototype.ignoreCase");
}
// ES6 21.2.5.7.
-void Builtins::Generate_RegExpPrototypeMultilineGetter(CodeStubAssembler* a) {
- Generate_FlagGetter(a, JSRegExp::kMultiline,
- v8::Isolate::kRegExpPrototypeOldFlagGetter,
- "RegExp.prototype.multiline");
+TF_BUILTIN(RegExpPrototypeMultilineGetter, RegExpBuiltinsAssembler) {
+ FlagGetter(JSRegExp::kMultiline, v8::Isolate::kRegExpPrototypeOldFlagGetter,
+ "RegExp.prototype.multiline");
}
// ES6 21.2.5.12.
-void Builtins::Generate_RegExpPrototypeStickyGetter(CodeStubAssembler* a) {
- Generate_FlagGetter(a, JSRegExp::kSticky,
- v8::Isolate::kRegExpPrototypeStickyGetter,
- "RegExp.prototype.sticky");
+TF_BUILTIN(RegExpPrototypeStickyGetter, RegExpBuiltinsAssembler) {
+ FlagGetter(JSRegExp::kSticky, v8::Isolate::kRegExpPrototypeStickyGetter,
+ "RegExp.prototype.sticky");
}
// ES6 21.2.5.15.
-void Builtins::Generate_RegExpPrototypeUnicodeGetter(CodeStubAssembler* a) {
- Generate_FlagGetter(a, JSRegExp::kUnicode,
- v8::Isolate::kRegExpPrototypeUnicodeGetter,
- "RegExp.prototype.unicode");
+TF_BUILTIN(RegExpPrototypeUnicodeGetter, RegExpBuiltinsAssembler) {
+ FlagGetter(JSRegExp::kUnicode, v8::Isolate::kRegExpPrototypeUnicodeGetter,
+ "RegExp.prototype.unicode");
}
// The properties $1..$9 are the first nine capturing substrings of the last
@@ -986,722 +1239,1040 @@ BUILTIN(RegExpRightContextGetter) {
return *isolate->factory()->NewSubString(last_subject, start_index, len);
}
-namespace {
-
// ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S )
-compiler::Node* RegExpExec(CodeStubAssembler* a, compiler::Node* context,
- compiler::Node* recv, compiler::Node* string) {
- typedef CodeStubAssembler::Variable Variable;
- typedef CodeStubAssembler::Label Label;
- typedef compiler::Node Node;
-
- Isolate* isolate = a->isolate();
+Node* RegExpBuiltinsAssembler::RegExpExec(Node* context, Node* regexp,
+ Node* string) {
+ Isolate* isolate = this->isolate();
- Node* const null = a->NullConstant();
+ Node* const null = NullConstant();
- Variable var_result(a, MachineRepresentation::kTagged);
- Label out(a), call_builtin_exec(a), slow_path(a, Label::kDeferred);
+ Variable var_result(this, MachineRepresentation::kTagged);
+ Label out(this), if_isfastpath(this), if_isslowpath(this);
- Node* const map = a->LoadMap(recv);
- BranchIfFastPath(a, context, map, &call_builtin_exec, &slow_path);
+ Node* const map = LoadMap(regexp);
+ BranchIfFastRegExp(context, regexp, map, &if_isfastpath, &if_isslowpath);
- a->Bind(&call_builtin_exec);
+ Bind(&if_isfastpath);
{
- Node* const result = RegExpPrototypeExecInternal(a, context, recv, string);
+ Node* const result = RegExpPrototypeExecBody(context, regexp, string, true);
var_result.Bind(result);
- a->Goto(&out);
+ Goto(&out);
}
- a->Bind(&slow_path);
+ Bind(&if_isslowpath);
{
// Take the slow path of fetching the exec property, calling it, and
// verifying its return value.
// Get the exec property.
- Node* const name = a->HeapConstant(isolate->factory()->exec_string());
- Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
- Node* const exec = a->CallStub(getproperty_callable, context, recv, name);
+ Node* const name = HeapConstant(isolate->factory()->exec_string());
+ Callable getproperty_callable = CodeFactory::GetProperty(isolate);
+ Node* const exec = CallStub(getproperty_callable, context, regexp, name);
// Is {exec} callable?
- Label if_iscallable(a), if_isnotcallable(a);
+ Label if_iscallable(this), if_isnotcallable(this);
- a->GotoIf(a->TaggedIsSmi(exec), &if_isnotcallable);
+ GotoIf(TaggedIsSmi(exec), &if_isnotcallable);
- Node* const exec_map = a->LoadMap(exec);
- a->Branch(a->IsCallableMap(exec_map), &if_iscallable, &if_isnotcallable);
+ Node* const exec_map = LoadMap(exec);
+ Branch(IsCallableMap(exec_map), &if_iscallable, &if_isnotcallable);
- a->Bind(&if_iscallable);
+ Bind(&if_iscallable);
{
Callable call_callable = CodeFactory::Call(isolate);
- Node* const result =
- a->CallJS(call_callable, context, exec, recv, string);
+ Node* const result = CallJS(call_callable, context, exec, regexp, string);
var_result.Bind(result);
- a->GotoIf(a->WordEqual(result, null), &out);
+ GotoIf(WordEqual(result, null), &out);
- ThrowIfNotJSReceiver(a, isolate, context, result,
+ ThrowIfNotJSReceiver(context, result,
MessageTemplate::kInvalidRegExpExecResult, "unused");
- a->Goto(&out);
+ Goto(&out);
}
- a->Bind(&if_isnotcallable);
+ Bind(&if_isnotcallable);
{
- a->ThrowIfNotInstanceType(context, recv, JS_REGEXP_TYPE,
- "RegExp.prototype.exec");
- a->Goto(&call_builtin_exec);
+ ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE,
+ "RegExp.prototype.exec");
+
+ Node* const result =
+ RegExpPrototypeExecBody(context, regexp, string, false);
+ var_result.Bind(result);
+ Goto(&out);
}
}
- a->Bind(&out);
+ Bind(&out);
return var_result.value();
}
-} // namespace
-
// ES#sec-regexp.prototype.test
// RegExp.prototype.test ( S )
-void Builtins::Generate_RegExpPrototypeTest(CodeStubAssembler* a) {
- typedef compiler::Node Node;
-
- Isolate* const isolate = a->isolate();
-
- Node* const maybe_receiver = a->Parameter(0);
- Node* const maybe_string = a->Parameter(1);
- Node* const context = a->Parameter(4);
+TF_BUILTIN(RegExpPrototypeTest, RegExpBuiltinsAssembler) {
+ Node* const maybe_receiver = Parameter(0);
+ Node* const maybe_string = Parameter(1);
+ Node* const context = Parameter(4);
// Ensure {maybe_receiver} is a JSReceiver.
- ThrowIfNotJSReceiver(a, isolate, context, maybe_receiver,
+ ThrowIfNotJSReceiver(context, maybe_receiver,
MessageTemplate::kIncompatibleMethodReceiver,
"RegExp.prototype.test");
Node* const receiver = maybe_receiver;
// Convert {maybe_string} to a String.
- Node* const string = a->ToString(context, maybe_string);
+ Node* const string = ToString(context, maybe_string);
- // Call exec.
- Node* const match_indices = RegExpExec(a, context, receiver, string);
+ Label fast_path(this), slow_path(this);
+ BranchIfFastRegExp(context, receiver, LoadMap(receiver), &fast_path,
+ &slow_path);
+
+ Bind(&fast_path);
+ {
+ Label if_didnotmatch(this);
+ RegExpPrototypeExecBodyWithoutResult(context, receiver, string,
+ &if_didnotmatch, true);
+ Return(TrueConstant());
- // Return true iff exec matched successfully.
- Node* const result = a->Select(a->WordEqual(match_indices, a->NullConstant()),
- a->FalseConstant(), a->TrueConstant());
- a->Return(result);
+ Bind(&if_didnotmatch);
+ Return(FalseConstant());
+ }
+
+ Bind(&slow_path);
+ {
+ // Call exec.
+ Node* const match_indices = RegExpExec(context, receiver, string);
+
+ // Return true iff exec matched successfully.
+ Node* const result =
+ SelectBooleanConstant(WordNotEqual(match_indices, NullConstant()));
+ Return(result);
+ }
}
-// ES#sec-regexp.prototype-@@match
-// RegExp.prototype [ @@match ] ( string )
-BUILTIN(RegExpPrototypeMatch) {
- HandleScope scope(isolate);
- CHECK_RECEIVER(JSReceiver, recv, "RegExp.prototype.@@match");
+Node* RegExpBuiltinsAssembler::AdvanceStringIndex(Node* const string,
+ Node* const index,
+ Node* const is_unicode,
+ bool is_fastpath) {
+ CSA_ASSERT(this, IsHeapNumberMap(LoadReceiverMap(index)));
+ if (is_fastpath) CSA_ASSERT(this, TaggedIsPositiveSmi(index));
+
+ // Default to last_index + 1.
+ Node* const index_plus_one = NumberInc(index);
+ Variable var_result(this, MachineRepresentation::kTagged, index_plus_one);
+
+ // Advancing the index has some subtle issues involving the distinction
+ // between Smis and HeapNumbers. There's three cases:
+ // * {index} is a Smi, {index_plus_one} is a Smi. The standard case.
+ // * {index} is a Smi, {index_plus_one} overflows into a HeapNumber.
+ // In this case we can return the result early, because
+ // {index_plus_one} > {string}.length.
+ // * {index} is a HeapNumber, {index_plus_one} is a HeapNumber. This can only
+ // occur when {index} is outside the Smi range since we normalize
+ // explicitly. Again we can return early.
+ if (is_fastpath) {
+ // Must be in Smi range on the fast path. We control the value of {index}
+ // on all call-sites and can never exceed the length of the string.
+ STATIC_ASSERT(String::kMaxLength + 2 < Smi::kMaxValue);
+ CSA_ASSERT(this, TaggedIsPositiveSmi(index_plus_one));
+ }
+
+ Label if_isunicode(this), out(this);
+ GotoIfNot(is_unicode, &out);
+
+ // Keep this unconditional (even on the fast path) just to be safe.
+ Branch(TaggedIsPositiveSmi(index_plus_one), &if_isunicode, &out);
- Handle<Object> string_obj = args.atOrUndefined(isolate, 1);
+ Bind(&if_isunicode);
+ {
+ Node* const string_length = LoadStringLength(string);
+ GotoIfNot(SmiLessThan(index_plus_one, string_length), &out);
+
+ Node* const lead = StringCharCodeAt(string, index);
+ GotoIfNot(Word32Equal(Word32And(lead, Int32Constant(0xFC00)),
+ Int32Constant(0xD800)),
+ &out);
- Handle<String> string;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, string,
- Object::ToString(isolate, string_obj));
+ Node* const trail = StringCharCodeAt(string, index_plus_one);
+ GotoIfNot(Word32Equal(Word32And(trail, Int32Constant(0xFC00)),
+ Int32Constant(0xDC00)),
+ &out);
- Handle<Object> global_obj;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, global_obj,
- JSReceiver::GetProperty(recv, isolate->factory()->global_string()));
- const bool global = global_obj->BooleanValue();
+ // At a surrogate pair, return index + 2.
+ Node* const index_plus_two = NumberInc(index_plus_one);
+ var_result.Bind(index_plus_two);
- if (!global) {
- RETURN_RESULT_OR_FAILURE(
- isolate,
- RegExpUtils::RegExpExec(isolate, recv, string,
- isolate->factory()->undefined_value()));
+ Goto(&out);
}
- Handle<Object> unicode_obj;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, unicode_obj,
- JSReceiver::GetProperty(recv, isolate->factory()->unicode_string()));
- const bool unicode = unicode_obj->BooleanValue();
+ Bind(&out);
+ return var_result.value();
+}
- RETURN_FAILURE_ON_EXCEPTION(isolate,
- RegExpUtils::SetLastIndex(isolate, recv, 0));
+namespace {
- static const int kInitialArraySize = 8;
- Handle<FixedArray> elems =
- isolate->factory()->NewFixedArrayWithHoles(kInitialArraySize);
+// Utility class implementing a growable fixed array through CSA.
+class GrowableFixedArray {
+ typedef CodeStubAssembler::Label Label;
+ typedef CodeStubAssembler::Variable Variable;
- int n = 0;
- for (;; n++) {
- Handle<Object> result;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, result,
- RegExpUtils::RegExpExec(isolate, recv, string,
- isolate->factory()->undefined_value()));
+ public:
+ explicit GrowableFixedArray(CodeStubAssembler* a)
+ : assembler_(a),
+ var_array_(a, MachineRepresentation::kTagged),
+ var_length_(a, MachineType::PointerRepresentation()),
+ var_capacity_(a, MachineType::PointerRepresentation()) {
+ Initialize();
+ }
- if (result->IsNull(isolate)) {
- if (n == 0) return isolate->heap()->null_value();
- break;
+ Node* length() const { return var_length_.value(); }
+
+ Variable* var_array() { return &var_array_; }
+ Variable* var_length() { return &var_length_; }
+ Variable* var_capacity() { return &var_capacity_; }
+
+ void Push(Node* const value) {
+ CodeStubAssembler* a = assembler_;
+
+ Node* const length = var_length_.value();
+ Node* const capacity = var_capacity_.value();
+
+ Label grow(a), store(a);
+ a->Branch(a->IntPtrEqual(capacity, length), &grow, &store);
+
+ a->Bind(&grow);
+ {
+ Node* const new_capacity = NewCapacity(a, capacity);
+ Node* const new_array = ResizeFixedArray(length, new_capacity);
+
+ var_capacity_.Bind(new_capacity);
+ var_array_.Bind(new_array);
+ a->Goto(&store);
+ }
+
+ a->Bind(&store);
+ {
+ Node* const array = var_array_.value();
+ a->StoreFixedArrayElement(array, length, value);
+
+ Node* const new_length = a->IntPtrAdd(length, a->IntPtrConstant(1));
+ var_length_.Bind(new_length);
}
+ }
+
+ Node* ToJSArray(Node* const context) {
+ CodeStubAssembler* a = assembler_;
+
+ const ElementsKind kind = FAST_ELEMENTS;
+
+ Node* const native_context = a->LoadNativeContext(context);
+ Node* const array_map = a->LoadJSArrayElementsMap(kind, native_context);
+
+ // Shrink to fit if necessary.
+ {
+ Label next(a);
- Handle<Object> match_obj;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match_obj,
- Object::GetElement(isolate, result, 0));
+ Node* const length = var_length_.value();
+ Node* const capacity = var_capacity_.value();
- Handle<String> match;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match,
- Object::ToString(isolate, match_obj));
+ a->GotoIf(a->WordEqual(length, capacity), &next);
- elems = FixedArray::SetAndGrow(elems, n, match);
+ Node* const array = ResizeFixedArray(length, length);
+ var_array_.Bind(array);
+ var_capacity_.Bind(length);
+ a->Goto(&next);
- if (match->length() == 0) {
- RETURN_FAILURE_ON_EXCEPTION(isolate, RegExpUtils::SetAdvancedStringIndex(
- isolate, recv, string, unicode));
+ a->Bind(&next);
}
+
+ Node* const result_length = a->SmiTag(length());
+ Node* const result = a->AllocateUninitializedJSArrayWithoutElements(
+ kind, array_map, result_length, nullptr);
+
+ // Note: We do not currently shrink the fixed array.
+
+ a->StoreObjectField(result, JSObject::kElementsOffset, var_array_.value());
+
+ return result;
}
- elems->Shrink(n);
- return *isolate->factory()->NewJSArrayWithElements(elems);
-}
+ private:
+ void Initialize() {
+ CodeStubAssembler* a = assembler_;
-namespace {
+ const ElementsKind kind = FAST_ELEMENTS;
-void Generate_RegExpPrototypeSearchBody(CodeStubAssembler* a,
- compiler::Node* const receiver,
- compiler::Node* const string,
- compiler::Node* const context,
- bool is_fastpath) {
- typedef CodeStubAssembler::Label Label;
- typedef compiler::Node Node;
+ static const int kInitialArraySize = 8;
+ Node* const capacity = a->IntPtrConstant(kInitialArraySize);
+ Node* const array = a->AllocateFixedArray(kind, capacity);
- Isolate* const isolate = a->isolate();
+ a->FillFixedArrayWithValue(kind, array, a->IntPtrConstant(0), capacity,
+ Heap::kTheHoleValueRootIndex);
- Node* const smi_zero = a->SmiConstant(Smi::kZero);
+ var_array_.Bind(array);
+ var_capacity_.Bind(capacity);
+ var_length_.Bind(a->IntPtrConstant(0));
+ }
- // Grab the initial value of last index.
- Node* const previous_last_index =
- is_fastpath ? FastLoadLastIndex(a, context, receiver)
- : SlowLoadLastIndex(a, context, receiver);
+ Node* NewCapacity(CodeStubAssembler* a, Node* const current_capacity) {
+ CSA_ASSERT(a, a->IntPtrGreaterThan(current_capacity, a->IntPtrConstant(0)));
- // Ensure last index is 0.
- if (is_fastpath) {
- FastStoreLastIndex(a, context, receiver, smi_zero);
- } else {
- Label next(a);
- a->GotoIf(a->SameValue(previous_last_index, smi_zero, context), &next);
+ // Growth rate is analog to JSObject::NewElementsCapacity:
+ // new_capacity = (current_capacity + (current_capacity >> 1)) + 16.
+
+ Node* const new_capacity = a->IntPtrAdd(
+ a->IntPtrAdd(current_capacity, a->WordShr(current_capacity, 1)),
+ a->IntPtrConstant(16));
- SlowStoreLastIndex(a, context, receiver, smi_zero);
- a->Goto(&next);
- a->Bind(&next);
+ return new_capacity;
}
- // Call exec.
- Node* const match_indices =
- is_fastpath ? RegExpPrototypeExecInternal(a, context, receiver, string)
- : RegExpExec(a, context, receiver, string);
+ // Creates a new array with {new_capacity} and copies the first
+ // {element_count} elements from the current array.
+ Node* ResizeFixedArray(Node* const element_count, Node* const new_capacity) {
+ CodeStubAssembler* a = assembler_;
- // Reset last index if necessary.
- if (is_fastpath) {
- FastStoreLastIndex(a, context, receiver, previous_last_index);
- } else {
- Label next(a);
- Node* const current_last_index = SlowLoadLastIndex(a, context, receiver);
+ CSA_ASSERT(a, a->IntPtrGreaterThan(element_count, a->IntPtrConstant(0)));
+ CSA_ASSERT(a, a->IntPtrGreaterThan(new_capacity, a->IntPtrConstant(0)));
+ CSA_ASSERT(a, a->IntPtrGreaterThanOrEqual(new_capacity, element_count));
+
+ const ElementsKind kind = FAST_ELEMENTS;
+ const WriteBarrierMode barrier_mode = UPDATE_WRITE_BARRIER;
+ const ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS;
+ const CodeStubAssembler::AllocationFlags flags =
+ CodeStubAssembler::kAllowLargeObjectAllocation;
- a->GotoIf(a->SameValue(current_last_index, previous_last_index, context),
- &next);
+ Node* const from_array = var_array_.value();
+ Node* const to_array =
+ a->AllocateFixedArray(kind, new_capacity, mode, flags);
+ a->CopyFixedArrayElements(kind, from_array, kind, to_array, element_count,
+ new_capacity, barrier_mode, mode);
- SlowStoreLastIndex(a, context, receiver, previous_last_index);
- a->Goto(&next);
- a->Bind(&next);
+ return to_array;
}
- // Return -1 if no match was found.
+ private:
+ CodeStubAssembler* const assembler_;
+ Variable var_array_;
+ Variable var_length_;
+ Variable var_capacity_;
+};
+
+} // namespace
+
+void RegExpBuiltinsAssembler::RegExpPrototypeMatchBody(Node* const context,
+ Node* const regexp,
+ Node* const string,
+ const bool is_fastpath) {
+ Isolate* const isolate = this->isolate();
+
+ Node* const null = NullConstant();
+ Node* const int_zero = IntPtrConstant(0);
+ Node* const smi_zero = SmiConstant(Smi::kZero);
+
+ Node* const is_global =
+ FlagGetter(context, regexp, JSRegExp::kGlobal, is_fastpath);
+
+ Label if_isglobal(this), if_isnotglobal(this);
+ Branch(is_global, &if_isglobal, &if_isnotglobal);
+
+ Bind(&if_isnotglobal);
{
- Label next(a);
- a->GotoUnless(a->WordEqual(match_indices, a->NullConstant()), &next);
- a->Return(a->SmiConstant(-1));
- a->Bind(&next);
+ Node* const result =
+ is_fastpath ? RegExpPrototypeExecBody(context, regexp, string, true)
+ : RegExpExec(context, regexp, string);
+ Return(result);
}
- // Return the index of the match.
+ Bind(&if_isglobal);
{
- Label fast_result(a), slow_result(a, Label::kDeferred);
+ Node* const is_unicode =
+ FlagGetter(context, regexp, JSRegExp::kUnicode, is_fastpath);
- Node* const native_context = a->LoadNativeContext(context);
- Node* const initial_regexp_result_map =
- a->LoadContextElement(native_context, Context::REGEXP_RESULT_MAP_INDEX);
- Node* const match_indices_map = a->LoadMap(match_indices);
+ StoreLastIndex(context, regexp, smi_zero, is_fastpath);
+
+ // Allocate an array to store the resulting match strings.
+
+ GrowableFixedArray array(this);
- a->Branch(a->WordEqual(match_indices_map, initial_regexp_result_map),
- &fast_result, &slow_result);
+ // Loop preparations. Within the loop, collect results from RegExpExec
+ // and store match strings in the array.
- a->Bind(&fast_result);
+ Variable* vars[] = {array.var_array(), array.var_length(),
+ array.var_capacity()};
+ Label loop(this, 3, vars), out(this);
+ Goto(&loop);
+
+ Bind(&loop);
{
- Node* const index =
- a->LoadObjectField(match_indices, JSRegExpResult::kIndexOffset,
- MachineType::AnyTagged());
- a->Return(index);
+ Variable var_match(this, MachineRepresentation::kTagged);
+
+ Label if_didmatch(this), if_didnotmatch(this);
+ if (is_fastpath) {
+ // On the fast path, grab the matching string from the raw match index
+ // array.
+ Node* const match_indices = RegExpPrototypeExecBodyWithoutResult(
+ context, regexp, string, &if_didnotmatch, true);
+
+ Node* const match_from = LoadFixedArrayElement(
+ match_indices, RegExpMatchInfo::kFirstCaptureIndex);
+ Node* const match_to = LoadFixedArrayElement(
+ match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1);
+
+ Node* match = SubString(context, string, match_from, match_to);
+ var_match.Bind(match);
+
+ Goto(&if_didmatch);
+ } else {
+ DCHECK(!is_fastpath);
+ Node* const result = RegExpExec(context, regexp, string);
+
+ Label load_match(this);
+ Branch(WordEqual(result, null), &if_didnotmatch, &load_match);
+
+ Bind(&load_match);
+ {
+ Label fast_result(this), slow_result(this);
+ BranchIfFastRegExpResult(context, LoadMap(result), &fast_result,
+ &slow_result);
+
+ Bind(&fast_result);
+ {
+ Node* const result_fixed_array = LoadElements(result);
+ Node* const match = LoadFixedArrayElement(result_fixed_array, 0);
+
+ // The match is guaranteed to be a string on the fast path.
+ CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(match)));
+
+ var_match.Bind(match);
+ Goto(&if_didmatch);
+ }
+
+ Bind(&slow_result);
+ {
+ // TODO(ishell): Use GetElement stub once it's available.
+ Node* const name = smi_zero;
+ Callable getproperty_callable = CodeFactory::GetProperty(isolate);
+ Node* const match =
+ CallStub(getproperty_callable, context, result, name);
+
+ var_match.Bind(ToString(context, match));
+ Goto(&if_didmatch);
+ }
+ }
+ }
+
+ Bind(&if_didnotmatch);
+ {
+ // Return null if there were no matches, otherwise just exit the loop.
+ GotoIfNot(IntPtrEqual(array.length(), int_zero), &out);
+ Return(null);
+ }
+
+ Bind(&if_didmatch);
+ {
+ Node* match = var_match.value();
+
+ // Store the match, growing the fixed array if needed.
+
+ array.Push(match);
+
+ // Advance last index if the match is the empty string.
+
+ Node* const match_length = LoadStringLength(match);
+ GotoIfNot(SmiEqual(match_length, smi_zero), &loop);
+
+ Node* last_index = LoadLastIndex(context, regexp, is_fastpath);
+ if (is_fastpath) {
+ CSA_ASSERT(this, TaggedIsPositiveSmi(last_index));
+ } else {
+ Callable tolength_callable = CodeFactory::ToLength(isolate);
+ last_index = CallStub(tolength_callable, context, last_index);
+ }
+
+ Node* const new_last_index =
+ AdvanceStringIndex(string, last_index, is_unicode, is_fastpath);
+
+ if (is_fastpath) {
+ // On the fast path, we can be certain that lastIndex can never be
+ // incremented to overflow the Smi range since the maximal string
+ // length is less than the maximal Smi value.
+ STATIC_ASSERT(String::kMaxLength < Smi::kMaxValue);
+ CSA_ASSERT(this, TaggedIsPositiveSmi(new_last_index));
+ }
+
+ StoreLastIndex(context, regexp, new_last_index, is_fastpath);
+
+ Goto(&loop);
+ }
}
- a->Bind(&slow_result);
+ Bind(&out);
{
- Node* const name = a->HeapConstant(isolate->factory()->index_string());
- Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
- Node* const index =
- a->CallStub(getproperty_callable, context, match_indices, name);
- a->Return(index);
+ // Wrap the match in a JSArray.
+
+ Node* const result = array.ToJSArray(context);
+ Return(result);
}
}
}
-} // namespace
-
-// ES#sec-regexp.prototype-@@search
-// RegExp.prototype [ @@search ] ( string )
-void Builtins::Generate_RegExpPrototypeSearch(CodeStubAssembler* a) {
- typedef CodeStubAssembler::Label Label;
- typedef compiler::Node Node;
-
- Isolate* const isolate = a->isolate();
-
- Node* const maybe_receiver = a->Parameter(0);
- Node* const maybe_string = a->Parameter(1);
- Node* const context = a->Parameter(4);
+// ES#sec-regexp.prototype-@@match
+// RegExp.prototype [ @@match ] ( string )
+TF_BUILTIN(RegExpPrototypeMatch, RegExpBuiltinsAssembler) {
+ Node* const maybe_receiver = Parameter(0);
+ Node* const maybe_string = Parameter(1);
+ Node* const context = Parameter(4);
// Ensure {maybe_receiver} is a JSReceiver.
- Node* const map =
- ThrowIfNotJSReceiver(a, isolate, context, maybe_receiver,
- MessageTemplate::kIncompatibleMethodReceiver,
- "RegExp.prototype.@@search");
+ ThrowIfNotJSReceiver(context, maybe_receiver,
+ MessageTemplate::kIncompatibleMethodReceiver,
+ "RegExp.prototype.@@match");
Node* const receiver = maybe_receiver;
// Convert {maybe_string} to a String.
- Node* const string = a->ToString(context, maybe_string);
+ Node* const string = ToString(context, maybe_string);
- Label fast_path(a), slow_path(a);
- BranchIfFastPath(a, context, map, &fast_path, &slow_path);
+ Label fast_path(this), slow_path(this);
+ BranchIfFastRegExp(context, receiver, LoadMap(receiver), &fast_path,
+ &slow_path);
- a->Bind(&fast_path);
- Generate_RegExpPrototypeSearchBody(a, receiver, string, context, true);
+ Bind(&fast_path);
+ RegExpPrototypeMatchBody(context, receiver, string, true);
- a->Bind(&slow_path);
- Generate_RegExpPrototypeSearchBody(a, receiver, string, context, false);
+ Bind(&slow_path);
+ RegExpPrototypeMatchBody(context, receiver, string, false);
}
-namespace {
+void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodyFast(
+ Node* const context, Node* const regexp, Node* const string) {
+ // Grab the initial value of last index.
+ Node* const previous_last_index = FastLoadLastIndex(regexp);
+
+ // Ensure last index is 0.
+ FastStoreLastIndex(regexp, SmiConstant(Smi::kZero));
-MUST_USE_RESULT MaybeHandle<Object> ToUint32(Isolate* isolate,
- Handle<Object> object,
- uint32_t* out) {
- if (object->IsUndefined(isolate)) {
- *out = kMaxUInt32;
- return object;
+ // Call exec.
+ Label if_didnotmatch(this);
+ Node* const match_indices = RegExpPrototypeExecBodyWithoutResult(
+ context, regexp, string, &if_didnotmatch, true);
+
+ // Successful match.
+ {
+ // Reset last index.
+ FastStoreLastIndex(regexp, previous_last_index);
+
+ // Return the index of the match.
+ Node* const index = LoadFixedArrayElement(
+ match_indices, RegExpMatchInfo::kFirstCaptureIndex);
+ Return(index);
}
- Handle<Object> number;
- ASSIGN_RETURN_ON_EXCEPTION(isolate, number, Object::ToNumber(object), Object);
- *out = NumberToUint32(*number);
- return object;
+ Bind(&if_didnotmatch);
+ {
+ // Reset last index and return -1.
+ FastStoreLastIndex(regexp, previous_last_index);
+ Return(SmiConstant(-1));
+ }
}
-bool AtSurrogatePair(Isolate* isolate, Handle<String> string, int index) {
- if (index + 1 >= string->length()) return false;
- const uint16_t first = string->Get(index);
- if (first < 0xD800 || first > 0xDBFF) return false;
- const uint16_t second = string->Get(index + 1);
- return (second >= 0xDC00 && second <= 0xDFFF);
-}
+void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow(
+ Node* const context, Node* const regexp, Node* const string) {
+ Isolate* const isolate = this->isolate();
-Handle<JSArray> NewJSArrayWithElements(Isolate* isolate,
- Handle<FixedArray> elems,
- int num_elems) {
- elems->Shrink(num_elems);
- return isolate->factory()->NewJSArrayWithElements(elems);
-}
+ Node* const smi_zero = SmiConstant(Smi::kZero);
-MaybeHandle<JSArray> RegExpSplit(Isolate* isolate, Handle<JSRegExp> regexp,
- Handle<String> string,
- Handle<Object> limit_obj) {
- Factory* factory = isolate->factory();
+ // Grab the initial value of last index.
+ Node* const previous_last_index = SlowLoadLastIndex(context, regexp);
- uint32_t limit;
- RETURN_ON_EXCEPTION(isolate, ToUint32(isolate, limit_obj, &limit), JSArray);
+ // Ensure last index is 0.
+ {
+ Label next(this);
+ GotoIf(SameValue(previous_last_index, smi_zero, context), &next);
- const int length = string->length();
+ SlowStoreLastIndex(context, regexp, smi_zero);
+ Goto(&next);
+ Bind(&next);
+ }
- if (limit == 0) return factory->NewJSArray(0);
+ // Call exec.
+ Node* const exec_result = RegExpExec(context, regexp, string);
- Handle<RegExpMatchInfo> last_match_info = isolate->regexp_last_match_info();
+ // Reset last index if necessary.
+ {
+ Label next(this);
+ Node* const current_last_index = SlowLoadLastIndex(context, regexp);
- if (length == 0) {
- Handle<Object> match_indices;
- ASSIGN_RETURN_ON_EXCEPTION(
- isolate, match_indices,
- RegExpImpl::Exec(regexp, string, 0, last_match_info), JSArray);
+ GotoIf(SameValue(current_last_index, previous_last_index, context), &next);
- if (!match_indices->IsNull(isolate)) return factory->NewJSArray(0);
+ SlowStoreLastIndex(context, regexp, previous_last_index);
+ Goto(&next);
- Handle<FixedArray> elems = factory->NewUninitializedFixedArray(1);
- elems->set(0, *string);
- return factory->NewJSArrayWithElements(elems);
+ Bind(&next);
}
- int current_index = 0;
- int start_index = 0;
- int start_match = 0;
+ // Return -1 if no match was found.
+ {
+ Label next(this);
+ GotoIfNot(WordEqual(exec_result, NullConstant()), &next);
+ Return(SmiConstant(-1));
+ Bind(&next);
+ }
- static const int kInitialArraySize = 8;
- Handle<FixedArray> elems = factory->NewFixedArrayWithHoles(kInitialArraySize);
- int num_elems = 0;
+ // Return the index of the match.
+ {
+ Label fast_result(this), slow_result(this, Label::kDeferred);
+ BranchIfFastRegExpResult(context, LoadMap(exec_result), &fast_result,
+ &slow_result);
- while (true) {
- if (start_index == length) {
- Handle<String> substr =
- factory->NewSubString(string, current_index, length);
- elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
- break;
+ Bind(&fast_result);
+ {
+ Node* const index =
+ LoadObjectField(exec_result, JSRegExpResult::kIndexOffset);
+ Return(index);
}
- Handle<Object> match_indices_obj;
- ASSIGN_RETURN_ON_EXCEPTION(
- isolate, match_indices_obj,
- RegExpImpl::Exec(regexp, string, start_index,
- isolate->regexp_last_match_info()),
- JSArray);
-
- if (match_indices_obj->IsNull(isolate)) {
- Handle<String> substr =
- factory->NewSubString(string, current_index, length);
- elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
- break;
+ Bind(&slow_result);
+ {
+ Node* const name = HeapConstant(isolate->factory()->index_string());
+ Callable getproperty_callable = CodeFactory::GetProperty(isolate);
+ Node* const index =
+ CallStub(getproperty_callable, context, exec_result, name);
+ Return(index);
}
+ }
+}
- auto match_indices = Handle<RegExpMatchInfo>::cast(match_indices_obj);
+// ES#sec-regexp.prototype-@@search
+// RegExp.prototype [ @@search ] ( string )
+TF_BUILTIN(RegExpPrototypeSearch, RegExpBuiltinsAssembler) {
+ Node* const maybe_receiver = Parameter(0);
+ Node* const maybe_string = Parameter(1);
+ Node* const context = Parameter(4);
- start_match = match_indices->Capture(0);
+ // Ensure {maybe_receiver} is a JSReceiver.
+ ThrowIfNotJSReceiver(context, maybe_receiver,
+ MessageTemplate::kIncompatibleMethodReceiver,
+ "RegExp.prototype.@@search");
+ Node* const receiver = maybe_receiver;
- if (start_match == length) {
- Handle<String> substr =
- factory->NewSubString(string, current_index, length);
- elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
- break;
- }
+ // Convert {maybe_string} to a String.
+ Node* const string = ToString(context, maybe_string);
- const int end_index = match_indices->Capture(1);
+ Label fast_path(this), slow_path(this);
+ BranchIfFastRegExp(context, receiver, LoadMap(receiver), &fast_path,
+ &slow_path);
- if (start_index == end_index && end_index == current_index) {
- const bool unicode = (regexp->GetFlags() & JSRegExp::kUnicode) != 0;
- if (unicode && AtSurrogatePair(isolate, string, start_index)) {
- start_index += 2;
- } else {
- start_index += 1;
- }
- continue;
- }
+ Bind(&fast_path);
+ RegExpPrototypeSearchBodyFast(context, receiver, string);
+
+ Bind(&slow_path);
+ RegExpPrototypeSearchBodySlow(context, receiver, string);
+}
+
+// Generates the fast path for @@split. {regexp} is an unmodified JSRegExp,
+// {string} is a String, and {limit} is a Smi.
+void RegExpBuiltinsAssembler::RegExpPrototypeSplitBody(Node* const context,
+ Node* const regexp,
+ Node* const string,
+ Node* const limit) {
+ Isolate* isolate = this->isolate();
+
+ Node* const null = NullConstant();
+ Node* const smi_zero = SmiConstant(0);
+ Node* const int_zero = IntPtrConstant(0);
+ Node* const int_limit = SmiUntag(limit);
+ const ElementsKind kind = FAST_ELEMENTS;
+ const ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS;
+
+ Node* const allocation_site = nullptr;
+ Node* const native_context = LoadNativeContext(context);
+ Node* const array_map = LoadJSArrayElementsMap(kind, native_context);
+
+ Label return_empty_array(this, Label::kDeferred);
+
+ // If limit is zero, return an empty array.
+ {
+ Label next(this), if_limitiszero(this, Label::kDeferred);
+ Branch(SmiEqual(limit, smi_zero), &return_empty_array, &next);
+ Bind(&next);
+ }
+
+ Node* const string_length = LoadStringLength(string);
+
+ // If passed the empty {string}, return either an empty array or a singleton
+ // array depending on whether the {regexp} matches.
+ {
+ Label next(this), if_stringisempty(this, Label::kDeferred);
+ Branch(SmiEqual(string_length, smi_zero), &if_stringisempty, &next);
+
+ Bind(&if_stringisempty);
{
- Handle<String> substr =
- factory->NewSubString(string, current_index, start_match);
- elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
- }
+ Node* const last_match_info = LoadContextElement(
+ native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
- if (static_cast<uint32_t>(num_elems) == limit) break;
+ Callable exec_callable = CodeFactory::RegExpExec(isolate);
+ Node* const match_indices = CallStub(exec_callable, context, regexp,
+ string, smi_zero, last_match_info);
- for (int i = 2; i < match_indices->NumberOfCaptureRegisters(); i += 2) {
- const int start = match_indices->Capture(i);
- const int end = match_indices->Capture(i + 1);
+ Label return_singleton_array(this);
+ Branch(WordEqual(match_indices, null), &return_singleton_array,
+ &return_empty_array);
- if (end != -1) {
- Handle<String> substr = factory->NewSubString(string, start, end);
- elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
- } else {
- elems = FixedArray::SetAndGrow(elems, num_elems++,
- factory->undefined_value());
- }
+ Bind(&return_singleton_array);
+ {
+ Node* const length = SmiConstant(1);
+ Node* const capacity = IntPtrConstant(1);
+ Node* const result = AllocateJSArray(kind, array_map, capacity, length,
+ allocation_site, mode);
+
+ Node* const fixed_array = LoadElements(result);
+ StoreFixedArrayElement(fixed_array, 0, string);
- if (static_cast<uint32_t>(num_elems) == limit) {
- return NewJSArrayWithElements(isolate, elems, num_elems);
+ Return(result);
}
}
- start_index = current_index = end_index;
+ Bind(&next);
}
- return NewJSArrayWithElements(isolate, elems, num_elems);
-}
+ // Loop preparations.
-// ES##sec-speciesconstructor
-// SpeciesConstructor ( O, defaultConstructor )
-MUST_USE_RESULT MaybeHandle<Object> SpeciesConstructor(
- Isolate* isolate, Handle<JSReceiver> recv,
- Handle<JSFunction> default_ctor) {
- Handle<Object> ctor_obj;
- ASSIGN_RETURN_ON_EXCEPTION(
- isolate, ctor_obj,
- JSObject::GetProperty(recv, isolate->factory()->constructor_string()),
- Object);
+ GrowableFixedArray array(this);
- if (ctor_obj->IsUndefined(isolate)) return default_ctor;
+ Variable var_last_matched_until(this, MachineRepresentation::kTagged);
+ Variable var_next_search_from(this, MachineRepresentation::kTagged);
- if (!ctor_obj->IsJSReceiver()) {
- THROW_NEW_ERROR(isolate,
- NewTypeError(MessageTemplate::kConstructorNotReceiver),
- Object);
- }
+ var_last_matched_until.Bind(smi_zero);
+ var_next_search_from.Bind(smi_zero);
- Handle<JSReceiver> ctor = Handle<JSReceiver>::cast(ctor_obj);
+ Variable* vars[] = {array.var_array(), array.var_length(),
+ array.var_capacity(), &var_last_matched_until,
+ &var_next_search_from};
+ const int vars_count = sizeof(vars) / sizeof(vars[0]);
+ Label loop(this, vars_count, vars), push_suffix_and_out(this), out(this);
+ Goto(&loop);
- Handle<Object> species;
- ASSIGN_RETURN_ON_EXCEPTION(
- isolate, species,
- JSObject::GetProperty(ctor, isolate->factory()->species_symbol()),
- Object);
+ Bind(&loop);
+ {
+ Node* const next_search_from = var_next_search_from.value();
+ Node* const last_matched_until = var_last_matched_until.value();
- if (species->IsNull(isolate) || species->IsUndefined(isolate)) {
- return default_ctor;
- }
+ // We're done if we've reached the end of the string.
+ {
+ Label next(this);
+ Branch(SmiEqual(next_search_from, string_length), &push_suffix_and_out,
+ &next);
+ Bind(&next);
+ }
- if (species->IsConstructor()) return species;
+ // Search for the given {regexp}.
- THROW_NEW_ERROR(
- isolate, NewTypeError(MessageTemplate::kSpeciesNotConstructor), Object);
-}
+ Node* const last_match_info = LoadContextElement(
+ native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
-} // namespace
+ Callable exec_callable = CodeFactory::RegExpExec(isolate);
+ Node* const match_indices = CallStub(exec_callable, context, regexp, string,
+ next_search_from, last_match_info);
-// ES#sec-regexp.prototype-@@split
-// RegExp.prototype [ @@split ] ( string, limit )
-BUILTIN(RegExpPrototypeSplit) {
- HandleScope scope(isolate);
- CHECK_RECEIVER(JSReceiver, recv, "RegExp.prototype.@@split");
+ // We're done if no match was found.
+ {
+ Label next(this);
+ Branch(WordEqual(match_indices, null), &push_suffix_and_out, &next);
+ Bind(&next);
+ }
- Factory* factory = isolate->factory();
+ Node* const match_from = LoadFixedArrayElement(
+ match_indices, RegExpMatchInfo::kFirstCaptureIndex);
- Handle<Object> string_obj = args.atOrUndefined(isolate, 1);
- Handle<Object> limit_obj = args.atOrUndefined(isolate, 2);
+ // We're done if the match starts beyond the string.
+ {
+ Label next(this);
+ Branch(WordEqual(match_from, string_length), &push_suffix_and_out, &next);
+ Bind(&next);
+ }
- Handle<String> string;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, string,
- Object::ToString(isolate, string_obj));
+ Node* const match_to = LoadFixedArrayElement(
+ match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1);
- if (RegExpUtils::IsUnmodifiedRegExp(isolate, recv)) {
- RETURN_RESULT_OR_FAILURE(
- isolate,
- RegExpSplit(isolate, Handle<JSRegExp>::cast(recv), string, limit_obj));
- }
+ // Advance index and continue if the match is empty.
+ {
+ Label next(this);
- Handle<JSFunction> regexp_fun = isolate->regexp_function();
- Handle<Object> ctor;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, ctor, SpeciesConstructor(isolate, recv, regexp_fun));
+ GotoIfNot(SmiEqual(match_to, next_search_from), &next);
+ GotoIfNot(SmiEqual(match_to, last_matched_until), &next);
- Handle<Object> flags_obj;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, flags_obj, JSObject::GetProperty(recv, factory->flags_string()));
+ Node* const is_unicode = FastFlagGetter(regexp, JSRegExp::kUnicode);
+ Node* const new_next_search_from =
+ AdvanceStringIndex(string, next_search_from, is_unicode, true);
+ var_next_search_from.Bind(new_next_search_from);
+ Goto(&loop);
- Handle<String> flags;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, flags,
- Object::ToString(isolate, flags_obj));
+ Bind(&next);
+ }
- Handle<String> u_str = factory->LookupSingleCharacterStringFromCode('u');
- const bool unicode = (String::IndexOf(isolate, flags, u_str, 0) >= 0);
+ // A valid match was found, add the new substring to the array.
+ {
+ Node* const from = last_matched_until;
+ Node* const to = match_from;
- Handle<String> y_str = factory->LookupSingleCharacterStringFromCode('y');
- const bool sticky = (String::IndexOf(isolate, flags, y_str, 0) >= 0);
+ Node* const substr = SubString(context, string, from, to);
+ array.Push(substr);
- Handle<String> new_flags = flags;
- if (!sticky) {
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, new_flags,
- factory->NewConsString(flags, y_str));
- }
+ GotoIf(WordEqual(array.length(), int_limit), &out);
+ }
- Handle<JSReceiver> splitter;
- {
- const int argc = 2;
+ // Add all captures to the array.
+ {
+ Node* const num_registers = LoadFixedArrayElement(
+ match_indices, RegExpMatchInfo::kNumberOfCapturesIndex);
+ Node* const int_num_registers = SmiUntag(num_registers);
- ScopedVector<Handle<Object>> argv(argc);
- argv[0] = recv;
- argv[1] = new_flags;
+ Variable var_reg(this, MachineType::PointerRepresentation());
+ var_reg.Bind(IntPtrConstant(2));
- Handle<JSFunction> ctor_fun = Handle<JSFunction>::cast(ctor);
- Handle<Object> splitter_obj;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, splitter_obj, Execution::New(ctor_fun, argc, argv.start()));
+ Variable* vars[] = {array.var_array(), array.var_length(),
+ array.var_capacity(), &var_reg};
+ const int vars_count = sizeof(vars) / sizeof(vars[0]);
+ Label nested_loop(this, vars_count, vars), nested_loop_out(this);
+ Branch(IntPtrLessThan(var_reg.value(), int_num_registers), &nested_loop,
+ &nested_loop_out);
- splitter = Handle<JSReceiver>::cast(splitter_obj);
- }
+ Bind(&nested_loop);
+ {
+ Node* const reg = var_reg.value();
+ Node* const from = LoadFixedArrayElement(
+ match_indices, reg,
+ RegExpMatchInfo::kFirstCaptureIndex * kPointerSize, mode);
+ Node* const to = LoadFixedArrayElement(
+ match_indices, reg,
+ (RegExpMatchInfo::kFirstCaptureIndex + 1) * kPointerSize, mode);
+
+ Label select_capture(this), select_undefined(this), store_value(this);
+ Variable var_value(this, MachineRepresentation::kTagged);
+ Branch(SmiEqual(to, SmiConstant(-1)), &select_undefined,
+ &select_capture);
+
+ Bind(&select_capture);
+ {
+ Node* const substr = SubString(context, string, from, to);
+ var_value.Bind(substr);
+ Goto(&store_value);
+ }
- uint32_t limit;
- RETURN_FAILURE_ON_EXCEPTION(isolate, ToUint32(isolate, limit_obj, &limit));
+ Bind(&select_undefined);
+ {
+ Node* const undefined = UndefinedConstant();
+ var_value.Bind(undefined);
+ Goto(&store_value);
+ }
- const int length = string->length();
+ Bind(&store_value);
+ {
+ array.Push(var_value.value());
+ GotoIf(WordEqual(array.length(), int_limit), &out);
- if (limit == 0) return *factory->NewJSArray(0);
+ Node* const new_reg = IntPtrAdd(reg, IntPtrConstant(2));
+ var_reg.Bind(new_reg);
- if (length == 0) {
- Handle<Object> result;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, result, RegExpUtils::RegExpExec(isolate, splitter, string,
- factory->undefined_value()));
+ Branch(IntPtrLessThan(new_reg, int_num_registers), &nested_loop,
+ &nested_loop_out);
+ }
+ }
- if (!result->IsNull(isolate)) return *factory->NewJSArray(0);
+ Bind(&nested_loop_out);
+ }
- Handle<FixedArray> elems = factory->NewUninitializedFixedArray(1);
- elems->set(0, *string);
- return *factory->NewJSArrayWithElements(elems);
+ var_last_matched_until.Bind(match_to);
+ var_next_search_from.Bind(match_to);
+ Goto(&loop);
}
- // TODO(jgruber): Wrap this in a helper class.
- static const int kInitialArraySize = 8;
- Handle<FixedArray> elems = factory->NewFixedArrayWithHoles(kInitialArraySize);
- int num_elems = 0;
+ Bind(&push_suffix_and_out);
+ {
+ Node* const from = var_last_matched_until.value();
+ Node* const to = string_length;
- int string_index = 0;
- int prev_string_index = 0;
- while (string_index < length) {
- RETURN_FAILURE_ON_EXCEPTION(
- isolate, RegExpUtils::SetLastIndex(isolate, splitter, string_index));
+ Node* const substr = SubString(context, string, from, to);
+ array.Push(substr);
- Handle<Object> result;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, result, RegExpUtils::RegExpExec(isolate, splitter, string,
- factory->undefined_value()));
+ Goto(&out);
+ }
- if (result->IsNull(isolate)) {
- string_index = RegExpUtils::AdvanceStringIndex(isolate, string,
- string_index, unicode);
- continue;
- }
+ Bind(&out);
+ {
+ Node* const result = array.ToJSArray(context);
+ Return(result);
+ }
- // TODO(jgruber): Extract toLength of some property into function.
- Handle<Object> last_index_obj;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, last_index_obj, RegExpUtils::GetLastIndex(isolate, splitter));
+ Bind(&return_empty_array);
+ {
+ Node* const length = smi_zero;
+ Node* const capacity = int_zero;
+ Node* const result = AllocateJSArray(kind, array_map, capacity, length,
+ allocation_site, mode);
+ Return(result);
+ }
+}
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, last_index_obj, Object::ToLength(isolate, last_index_obj));
- const int last_index = Handle<Smi>::cast(last_index_obj)->value();
-
- const int end = std::min(last_index, length);
- if (end == prev_string_index) {
- string_index = RegExpUtils::AdvanceStringIndex(isolate, string,
- string_index, unicode);
- continue;
- }
+// Helper that skips a few initial checks.
+TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) {
+ typedef RegExpSplitDescriptor Descriptor;
- {
- Handle<String> substr =
- factory->NewSubString(string, prev_string_index, string_index);
- elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
- if (static_cast<uint32_t>(num_elems) == limit) {
- return *NewJSArrayWithElements(isolate, elems, num_elems);
- }
- }
+ Node* const regexp = Parameter(Descriptor::kReceiver);
+ Node* const string = Parameter(Descriptor::kString);
+ Node* const maybe_limit = Parameter(Descriptor::kLimit);
+ Node* const context = Parameter(Descriptor::kContext);
- prev_string_index = end;
+ CSA_ASSERT(this, IsFastRegExpMap(context, regexp, LoadMap(regexp)));
+ CSA_ASSERT(this, IsString(string));
- Handle<Object> num_captures_obj;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, num_captures_obj,
- Object::GetProperty(result, isolate->factory()->length_string()));
+ // TODO(jgruber): Even if map checks send us to the fast path, we still need
+ // to verify the constructor property and jump to the slow path if it has
+ // been changed.
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, num_captures_obj, Object::ToLength(isolate, num_captures_obj));
- const int num_captures =
- std::max(Handle<Smi>::cast(num_captures_obj)->value(), 0);
-
- for (int i = 1; i < num_captures; i++) {
- Handle<Object> capture;
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
- isolate, capture, Object::GetElement(isolate, result, i));
- elems = FixedArray::SetAndGrow(elems, num_elems++, capture);
- if (static_cast<uint32_t>(num_elems) == limit) {
- return *NewJSArrayWithElements(isolate, elems, num_elems);
- }
+ // Convert {maybe_limit} to a uint32, capping at the maximal smi value.
+ Variable var_limit(this, MachineRepresentation::kTagged, maybe_limit);
+ Label if_limitissmimax(this), limit_done(this), runtime(this);
+
+ GotoIf(IsUndefined(maybe_limit), &if_limitissmimax);
+ GotoIf(TaggedIsPositiveSmi(maybe_limit), &limit_done);
+
+ Node* const limit = ToUint32(context, maybe_limit);
+ {
+ // ToUint32(limit) could potentially change the shape of the RegExp
+ // object. Recheck that we are still on the fast path and bail to runtime
+ // otherwise.
+ {
+ Label next(this);
+ BranchIfFastRegExp(context, regexp, LoadMap(regexp), &next, &runtime);
+ Bind(&next);
}
- string_index = prev_string_index;
+ GotoIfNot(TaggedIsSmi(limit), &if_limitissmimax);
+
+ var_limit.Bind(limit);
+ Goto(&limit_done);
}
+ Bind(&if_limitissmimax);
{
- Handle<String> substr =
- factory->NewSubString(string, prev_string_index, length);
- elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
+ // TODO(jgruber): In this case, we can probably avoid generation of limit
+ // checks in Generate_RegExpPrototypeSplitBody.
+ var_limit.Bind(SmiConstant(Smi::kMaxValue));
+ Goto(&limit_done);
}
- return *NewJSArrayWithElements(isolate, elems, num_elems);
+ Bind(&limit_done);
+ {
+ Node* const limit = var_limit.value();
+ RegExpPrototypeSplitBody(context, regexp, string, limit);
+ }
+
+ Bind(&runtime);
+ {
+ // The runtime call passes in limit to ensure the second ToUint32(limit)
+ // call is not observable.
+ CSA_ASSERT(this, IsHeapNumberMap(LoadReceiverMap(limit)));
+ Return(CallRuntime(Runtime::kRegExpSplit, context, regexp, string, limit));
+ }
}
-namespace {
+// ES#sec-regexp.prototype-@@split
+// RegExp.prototype [ @@split ] ( string, limit )
+TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) {
+ Node* const maybe_receiver = Parameter(0);
+ Node* const maybe_string = Parameter(1);
+ Node* const maybe_limit = Parameter(2);
+ Node* const context = Parameter(5);
+
+ // Ensure {maybe_receiver} is a JSReceiver.
+ ThrowIfNotJSReceiver(context, maybe_receiver,
+ MessageTemplate::kIncompatibleMethodReceiver,
+ "RegExp.prototype.@@split");
+ Node* const receiver = maybe_receiver;
+
+ // Convert {maybe_string} to a String.
+ Node* const string = ToString(context, maybe_string);
+
+ Label stub(this), runtime(this, Label::kDeferred);
+ BranchIfFastRegExp(context, receiver, LoadMap(receiver), &stub, &runtime);
-compiler::Node* ReplaceGlobalCallableFastPath(
- CodeStubAssembler* a, compiler::Node* context, compiler::Node* regexp,
- compiler::Node* subject_string, compiler::Node* replace_callable) {
+ Bind(&stub);
+ Callable split_callable = CodeFactory::RegExpSplit(isolate());
+ Return(CallStub(split_callable, context, receiver, string, maybe_limit));
+
+ Bind(&runtime);
+ Return(CallRuntime(Runtime::kRegExpSplit, context, receiver, string,
+ maybe_limit));
+}
+
+Node* RegExpBuiltinsAssembler::ReplaceGlobalCallableFastPath(
+ Node* context, Node* regexp, Node* string, Node* replace_callable) {
// The fast path is reached only if {receiver} is a global unmodified
// JSRegExp instance and {replace_callable} is callable.
- typedef CodeStubAssembler::Variable Variable;
- typedef CodeStubAssembler::Label Label;
- typedef compiler::Node Node;
-
- Isolate* const isolate = a->isolate();
+ Isolate* const isolate = this->isolate();
- Node* const null = a->NullConstant();
- Node* const undefined = a->UndefinedConstant();
- Node* const int_zero = a->IntPtrConstant(0);
- Node* const int_one = a->IntPtrConstant(1);
- Node* const smi_zero = a->SmiConstant(Smi::kZero);
+ Node* const null = NullConstant();
+ Node* const undefined = UndefinedConstant();
+ Node* const int_zero = IntPtrConstant(0);
+ Node* const int_one = IntPtrConstant(1);
+ Node* const smi_zero = SmiConstant(Smi::kZero);
- Node* const native_context = a->LoadNativeContext(context);
+ Node* const native_context = LoadNativeContext(context);
- Label out(a);
- Variable var_result(a, MachineRepresentation::kTagged);
+ Label out(this);
+ Variable var_result(this, MachineRepresentation::kTagged);
// Set last index to 0.
- FastStoreLastIndex(a, context, regexp, smi_zero);
+ FastStoreLastIndex(regexp, smi_zero);
// Allocate {result_array}.
Node* result_array;
{
ElementsKind kind = FAST_ELEMENTS;
- Node* const array_map = a->LoadJSArrayElementsMap(kind, native_context);
- Node* const capacity = a->IntPtrConstant(16);
+ Node* const array_map = LoadJSArrayElementsMap(kind, native_context);
+ Node* const capacity = IntPtrConstant(16);
Node* const length = smi_zero;
Node* const allocation_site = nullptr;
- CodeStubAssembler::ParameterMode capacity_mode =
- CodeStubAssembler::INTPTR_PARAMETERS;
+ ParameterMode capacity_mode = CodeStubAssembler::INTPTR_PARAMETERS;
- result_array = a->AllocateJSArray(kind, array_map, capacity, length,
- allocation_site, capacity_mode);
+ result_array = AllocateJSArray(kind, array_map, capacity, length,
+ allocation_site, capacity_mode);
}
// Call into runtime for RegExpExecMultiple.
- Node* last_match_info = a->LoadContextElement(
- native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
- Node* const res =
- a->CallRuntime(Runtime::kRegExpExecMultiple, context, regexp,
- subject_string, last_match_info, result_array);
+ Node* last_match_info =
+ LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
+ Node* const res = CallRuntime(Runtime::kRegExpExecMultiple, context, regexp,
+ string, last_match_info, result_array);
// Reset last index to 0.
- FastStoreLastIndex(a, context, regexp, smi_zero);
+ FastStoreLastIndex(regexp, smi_zero);
// If no matches, return the subject string.
- var_result.Bind(subject_string);
- a->GotoIf(a->WordEqual(res, null), &out);
+ var_result.Bind(string);
+ GotoIf(WordEqual(res, null), &out);
// Reload last match info since it might have changed.
- last_match_info = a->LoadContextElement(
- native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
+ last_match_info =
+ LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
- Node* const res_length = a->LoadJSArrayLength(res);
- Node* const res_elems = a->LoadElements(res);
- CSA_ASSERT(a, a->HasInstanceType(res_elems, FIXED_ARRAY_TYPE));
+ Node* const res_length = LoadJSArrayLength(res);
+ Node* const res_elems = LoadElements(res);
+ CSA_ASSERT(this, HasInstanceType(res_elems, FIXED_ARRAY_TYPE));
- CodeStubAssembler::ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS;
- Node* const num_capture_registers = a->LoadFixedArrayElement(
- last_match_info,
- a->IntPtrConstant(RegExpMatchInfo::kNumberOfCapturesIndex), 0, mode);
+ Node* const num_capture_registers = LoadFixedArrayElement(
+ last_match_info, RegExpMatchInfo::kNumberOfCapturesIndex);
- Label if_hasexplicitcaptures(a), if_noexplicitcaptures(a), create_result(a);
- a->Branch(a->SmiEqual(num_capture_registers, a->SmiConstant(Smi::FromInt(2))),
- &if_noexplicitcaptures, &if_hasexplicitcaptures);
+ Label if_hasexplicitcaptures(this), if_noexplicitcaptures(this),
+ create_result(this);
+ Branch(SmiEqual(num_capture_registers, SmiConstant(Smi::FromInt(2))),
+ &if_noexplicitcaptures, &if_hasexplicitcaptures);
- a->Bind(&if_noexplicitcaptures);
+ Bind(&if_noexplicitcaptures);
{
// If the number of captures is two then there are no explicit captures in
// the regexp, just the implicit capture that captures the whole match. In
@@ -1710,394 +2281,388 @@ compiler::Node* ReplaceGlobalCallableFastPath(
// input string and some replacements that were returned from the replace
// function.
- Variable var_match_start(a, MachineRepresentation::kTagged);
+ Variable var_match_start(this, MachineRepresentation::kTagged);
var_match_start.Bind(smi_zero);
- Node* const end = a->SmiUntag(res_length);
- Variable var_i(a, MachineType::PointerRepresentation());
+ Node* const end = SmiUntag(res_length);
+ Variable var_i(this, MachineType::PointerRepresentation());
var_i.Bind(int_zero);
Variable* vars[] = {&var_i, &var_match_start};
- Label loop(a, 2, vars);
- a->Goto(&loop);
- a->Bind(&loop);
+ Label loop(this, 2, vars);
+ Goto(&loop);
+ Bind(&loop);
{
Node* const i = var_i.value();
- a->GotoUnless(a->IntPtrLessThan(i, end), &create_result);
+ GotoIfNot(IntPtrLessThan(i, end), &create_result);
- CodeStubAssembler::ParameterMode mode =
- CodeStubAssembler::INTPTR_PARAMETERS;
- Node* const elem = a->LoadFixedArrayElement(res_elems, i, 0, mode);
+ Node* const elem = LoadFixedArrayElement(res_elems, i);
- Label if_issmi(a), if_isstring(a), loop_epilogue(a);
- a->Branch(a->TaggedIsSmi(elem), &if_issmi, &if_isstring);
+ Label if_issmi(this), if_isstring(this), loop_epilogue(this);
+ Branch(TaggedIsSmi(elem), &if_issmi, &if_isstring);
- a->Bind(&if_issmi);
+ Bind(&if_issmi);
{
// Integers represent slices of the original string.
- Label if_isnegativeorzero(a), if_ispositive(a);
- a->BranchIfSmiLessThanOrEqual(elem, smi_zero, &if_isnegativeorzero,
- &if_ispositive);
+ Label if_isnegativeorzero(this), if_ispositive(this);
+ BranchIfSmiLessThanOrEqual(elem, smi_zero, &if_isnegativeorzero,
+ &if_ispositive);
- a->Bind(&if_ispositive);
+ Bind(&if_ispositive);
{
- Node* const int_elem = a->SmiUntag(elem);
+ Node* const int_elem = SmiUntag(elem);
Node* const new_match_start =
- a->IntPtrAdd(a->WordShr(int_elem, a->IntPtrConstant(11)),
- a->WordAnd(int_elem, a->IntPtrConstant(0x7ff)));
- var_match_start.Bind(a->SmiTag(new_match_start));
- a->Goto(&loop_epilogue);
+ IntPtrAdd(WordShr(int_elem, IntPtrConstant(11)),
+ WordAnd(int_elem, IntPtrConstant(0x7ff)));
+ var_match_start.Bind(SmiTag(new_match_start));
+ Goto(&loop_epilogue);
}
- a->Bind(&if_isnegativeorzero);
+ Bind(&if_isnegativeorzero);
{
- Node* const next_i = a->IntPtrAdd(i, int_one);
+ Node* const next_i = IntPtrAdd(i, int_one);
var_i.Bind(next_i);
- Node* const next_elem =
- a->LoadFixedArrayElement(res_elems, next_i, 0, mode);
+ Node* const next_elem = LoadFixedArrayElement(res_elems, next_i);
- Node* const new_match_start = a->SmiSub(next_elem, elem);
+ Node* const new_match_start = SmiSub(next_elem, elem);
var_match_start.Bind(new_match_start);
- a->Goto(&loop_epilogue);
+ Goto(&loop_epilogue);
}
}
- a->Bind(&if_isstring);
+ Bind(&if_isstring);
{
- CSA_ASSERT(a, a->IsStringInstanceType(a->LoadInstanceType(elem)));
+ CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(elem)));
Callable call_callable = CodeFactory::Call(isolate);
Node* const replacement_obj =
- a->CallJS(call_callable, context, replace_callable, undefined, elem,
- var_match_start.value(), subject_string);
+ CallJS(call_callable, context, replace_callable, undefined, elem,
+ var_match_start.value(), string);
- Node* const replacement_str = a->ToString(context, replacement_obj);
- a->StoreFixedArrayElement(res_elems, i, replacement_str);
+ Node* const replacement_str = ToString(context, replacement_obj);
+ StoreFixedArrayElement(res_elems, i, replacement_str);
- Node* const elem_length = a->LoadStringLength(elem);
+ Node* const elem_length = LoadStringLength(elem);
Node* const new_match_start =
- a->SmiAdd(var_match_start.value(), elem_length);
+ SmiAdd(var_match_start.value(), elem_length);
var_match_start.Bind(new_match_start);
- a->Goto(&loop_epilogue);
+ Goto(&loop_epilogue);
}
- a->Bind(&loop_epilogue);
+ Bind(&loop_epilogue);
{
- var_i.Bind(a->IntPtrAdd(var_i.value(), int_one));
- a->Goto(&loop);
+ var_i.Bind(IntPtrAdd(var_i.value(), int_one));
+ Goto(&loop);
}
}
}
- a->Bind(&if_hasexplicitcaptures);
+ Bind(&if_hasexplicitcaptures);
{
- CodeStubAssembler::ParameterMode mode =
- CodeStubAssembler::INTPTR_PARAMETERS;
-
Node* const from = int_zero;
- Node* const to = a->SmiUntag(res_length);
+ Node* const to = SmiUntag(res_length);
const int increment = 1;
- a->BuildFastLoop(
- MachineType::PointerRepresentation(), from, to,
- [res_elems, isolate, native_context, context, undefined,
- replace_callable, mode](CodeStubAssembler* a, Node* index) {
- Node* const elem =
- a->LoadFixedArrayElement(res_elems, index, 0, mode);
+ BuildFastLoop(
+ from, to,
+ [this, res_elems, isolate, native_context, context, undefined,
+ replace_callable](Node* index) {
+ Node* const elem = LoadFixedArrayElement(res_elems, index);
- Label do_continue(a);
- a->GotoIf(a->TaggedIsSmi(elem), &do_continue);
+ Label do_continue(this);
+ GotoIf(TaggedIsSmi(elem), &do_continue);
// elem must be an Array.
// Use the apply argument as backing for global RegExp properties.
- CSA_ASSERT(a, a->HasInstanceType(elem, JS_ARRAY_TYPE));
+ CSA_ASSERT(this, HasInstanceType(elem, JS_ARRAY_TYPE));
// TODO(jgruber): Remove indirection through Call->ReflectApply.
Callable call_callable = CodeFactory::Call(isolate);
- Node* const reflect_apply = a->LoadContextElement(
- native_context, Context::REFLECT_APPLY_INDEX);
+ Node* const reflect_apply =
+ LoadContextElement(native_context, Context::REFLECT_APPLY_INDEX);
Node* const replacement_obj =
- a->CallJS(call_callable, context, reflect_apply, undefined,
- replace_callable, undefined, elem);
+ CallJS(call_callable, context, reflect_apply, undefined,
+ replace_callable, undefined, elem);
// Overwrite the i'th element in the results with the string we got
// back from the callback function.
- Node* const replacement_str = a->ToString(context, replacement_obj);
- a->StoreFixedArrayElement(res_elems, index, replacement_str,
- UPDATE_WRITE_BARRIER, mode);
+ Node* const replacement_str = ToString(context, replacement_obj);
+ StoreFixedArrayElement(res_elems, index, replacement_str);
- a->Goto(&do_continue);
- a->Bind(&do_continue);
+ Goto(&do_continue);
+ Bind(&do_continue);
},
- increment, CodeStubAssembler::IndexAdvanceMode::kPost);
+ increment, CodeStubAssembler::INTPTR_PARAMETERS,
+ CodeStubAssembler::IndexAdvanceMode::kPost);
- a->Goto(&create_result);
+ Goto(&create_result);
}
- a->Bind(&create_result);
+ Bind(&create_result);
{
- Node* const result = a->CallRuntime(Runtime::kStringBuilderConcat, context,
- res, res_length, subject_string);
+ Node* const result = CallRuntime(Runtime::kStringBuilderConcat, context,
+ res, res_length, string);
var_result.Bind(result);
- a->Goto(&out);
+ Goto(&out);
}
- a->Bind(&out);
+ Bind(&out);
return var_result.value();
}
-compiler::Node* ReplaceSimpleStringFastPath(CodeStubAssembler* a,
- compiler::Node* context,
- compiler::Node* regexp,
- compiler::Node* subject_string,
- compiler::Node* replace_string) {
+Node* RegExpBuiltinsAssembler::ReplaceSimpleStringFastPath(
+ Node* context, Node* regexp, Node* string, Node* replace_string) {
// The fast path is reached only if {receiver} is an unmodified
// JSRegExp instance, {replace_value} is non-callable, and
// ToString({replace_value}) does not contain '$', i.e. we're doing a simple
// string replacement.
- typedef CodeStubAssembler::Variable Variable;
- typedef CodeStubAssembler::Label Label;
- typedef compiler::Node Node;
-
- Isolate* const isolate = a->isolate();
+ Node* const int_zero = IntPtrConstant(0);
+ Node* const smi_zero = SmiConstant(Smi::kZero);
- Node* const null = a->NullConstant();
- Node* const int_zero = a->IntPtrConstant(0);
- Node* const smi_zero = a->SmiConstant(Smi::kZero);
-
- Label out(a);
- Variable var_result(a, MachineRepresentation::kTagged);
+ Label out(this);
+ Variable var_result(this, MachineRepresentation::kTagged);
// Load the last match info.
- Node* const native_context = a->LoadNativeContext(context);
- Node* const last_match_info = a->LoadContextElement(
- native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
+ Node* const native_context = LoadNativeContext(context);
+ Node* const last_match_info =
+ LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
// Is {regexp} global?
- Label if_isglobal(a), if_isnonglobal(a);
- Node* const flags = a->LoadObjectField(regexp, JSRegExp::kFlagsOffset);
+ Label if_isglobal(this), if_isnonglobal(this);
+ Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset);
Node* const is_global =
- a->WordAnd(a->SmiUntag(flags), a->IntPtrConstant(JSRegExp::kGlobal));
- a->Branch(a->WordEqual(is_global, int_zero), &if_isnonglobal, &if_isglobal);
+ WordAnd(SmiUntag(flags), IntPtrConstant(JSRegExp::kGlobal));
+ Branch(WordEqual(is_global, int_zero), &if_isnonglobal, &if_isglobal);
- a->Bind(&if_isglobal);
+ Bind(&if_isglobal);
{
// Hand off global regexps to runtime.
- FastStoreLastIndex(a, context, regexp, smi_zero);
+ FastStoreLastIndex(regexp, smi_zero);
Node* const result =
- a->CallRuntime(Runtime::kStringReplaceGlobalRegExpWithString, context,
- subject_string, regexp, replace_string, last_match_info);
+ CallRuntime(Runtime::kStringReplaceGlobalRegExpWithString, context,
+ string, regexp, replace_string, last_match_info);
var_result.Bind(result);
- a->Goto(&out);
+ Goto(&out);
}
- a->Bind(&if_isnonglobal);
+ Bind(&if_isnonglobal);
{
// Run exec, then manually construct the resulting string.
- Callable exec_callable = CodeFactory::RegExpExec(isolate);
- Node* const match_indices =
- a->CallStub(exec_callable, context, regexp, subject_string, smi_zero,
- last_match_info);
-
- Label if_matched(a), if_didnotmatch(a);
- a->Branch(a->WordEqual(match_indices, null), &if_didnotmatch, &if_matched);
-
- a->Bind(&if_didnotmatch);
- {
- FastStoreLastIndex(a, context, regexp, smi_zero);
- var_result.Bind(subject_string);
- a->Goto(&out);
- }
+ Label if_didnotmatch(this);
+ Node* const match_indices = RegExpPrototypeExecBodyWithoutResult(
+ context, regexp, string, &if_didnotmatch, true);
- a->Bind(&if_matched);
+ // Successful match.
{
- CodeStubAssembler::ParameterMode mode =
- CodeStubAssembler::INTPTR_PARAMETERS;
-
Node* const subject_start = smi_zero;
- Node* const match_start = a->LoadFixedArrayElement(
- match_indices, a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex),
- 0, mode);
- Node* const match_end = a->LoadFixedArrayElement(
- match_indices,
- a->IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 1), 0, mode);
- Node* const subject_end = a->LoadStringLength(subject_string);
-
- Label if_replaceisempty(a), if_replaceisnotempty(a);
- Node* const replace_length = a->LoadStringLength(replace_string);
- a->Branch(a->SmiEqual(replace_length, smi_zero), &if_replaceisempty,
- &if_replaceisnotempty);
-
- a->Bind(&if_replaceisempty);
+ Node* const match_start = LoadFixedArrayElement(
+ match_indices, RegExpMatchInfo::kFirstCaptureIndex);
+ Node* const match_end = LoadFixedArrayElement(
+ match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1);
+ Node* const subject_end = LoadStringLength(string);
+
+ Label if_replaceisempty(this), if_replaceisnotempty(this);
+ Node* const replace_length = LoadStringLength(replace_string);
+ Branch(SmiEqual(replace_length, smi_zero), &if_replaceisempty,
+ &if_replaceisnotempty);
+
+ Bind(&if_replaceisempty);
{
// TODO(jgruber): We could skip many of the checks that using SubString
// here entails.
Node* const first_part =
- a->SubString(context, subject_string, subject_start, match_start);
+ SubString(context, string, subject_start, match_start);
Node* const second_part =
- a->SubString(context, subject_string, match_end, subject_end);
+ SubString(context, string, match_end, subject_end);
- Node* const result = a->StringAdd(context, first_part, second_part);
+ Node* const result = StringAdd(context, first_part, second_part);
var_result.Bind(result);
- a->Goto(&out);
+ Goto(&out);
}
- a->Bind(&if_replaceisnotempty);
+ Bind(&if_replaceisnotempty);
{
Node* const first_part =
- a->SubString(context, subject_string, subject_start, match_start);
+ SubString(context, string, subject_start, match_start);
Node* const second_part = replace_string;
Node* const third_part =
- a->SubString(context, subject_string, match_end, subject_end);
+ SubString(context, string, match_end, subject_end);
- Node* result = a->StringAdd(context, first_part, second_part);
- result = a->StringAdd(context, result, third_part);
+ Node* result = StringAdd(context, first_part, second_part);
+ result = StringAdd(context, result, third_part);
var_result.Bind(result);
- a->Goto(&out);
+ Goto(&out);
}
}
+
+ Bind(&if_didnotmatch);
+ {
+ var_result.Bind(string);
+ Goto(&out);
+ }
}
- a->Bind(&out);
+ Bind(&out);
return var_result.value();
}
-} // namespace
-
-// ES#sec-regexp.prototype-@@replace
-// RegExp.prototype [ @@replace ] ( string, replaceValue )
-void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
- typedef CodeStubAssembler::Label Label;
- typedef compiler::Node Node;
+// Helper that skips a few initial checks.
+TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) {
+ typedef RegExpReplaceDescriptor Descriptor;
- Isolate* const isolate = a->isolate();
+ Node* const regexp = Parameter(Descriptor::kReceiver);
+ Node* const string = Parameter(Descriptor::kString);
+ Node* const replace_value = Parameter(Descriptor::kReplaceValue);
+ Node* const context = Parameter(Descriptor::kContext);
- Node* const maybe_receiver = a->Parameter(0);
- Node* const maybe_string = a->Parameter(1);
- Node* const replace_value = a->Parameter(2);
- Node* const context = a->Parameter(5);
+ CSA_ASSERT(this, IsFastRegExpMap(context, regexp, LoadMap(regexp)));
+ CSA_ASSERT(this, IsString(string));
- Node* const int_zero = a->IntPtrConstant(0);
-
- // Ensure {maybe_receiver} is a JSReceiver.
- Node* const map =
- ThrowIfNotJSReceiver(a, isolate, context, maybe_receiver,
- MessageTemplate::kIncompatibleMethodReceiver,
- "RegExp.prototype.@@replace");
- Node* const receiver = maybe_receiver;
-
- // Convert {maybe_string} to a String.
- Callable tostring_callable = CodeFactory::ToString(isolate);
- Node* const string = a->CallStub(tostring_callable, context, maybe_string);
-
- // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance?
- Label checkreplacecallable(a), runtime(a, Label::kDeferred), fastpath(a);
- BranchIfFastPath(a, context, map, &checkreplacecallable, &runtime);
-
- a->Bind(&checkreplacecallable);
- Node* const regexp = receiver;
+ Label checkreplacestring(this), if_iscallable(this),
+ runtime(this, Label::kDeferred);
// 2. Is {replace_value} callable?
- Label checkreplacestring(a), if_iscallable(a);
- a->GotoIf(a->TaggedIsSmi(replace_value), &checkreplacestring);
-
- Node* const replace_value_map = a->LoadMap(replace_value);
- a->Branch(a->IsCallableMap(replace_value_map), &if_iscallable,
- &checkreplacestring);
+ GotoIf(TaggedIsSmi(replace_value), &checkreplacestring);
+ Branch(IsCallableMap(LoadMap(replace_value)), &if_iscallable,
+ &checkreplacestring);
// 3. Does ToString({replace_value}) contain '$'?
- a->Bind(&checkreplacestring);
+ Bind(&checkreplacestring);
{
+ Callable tostring_callable = CodeFactory::ToString(isolate());
Node* const replace_string =
- a->CallStub(tostring_callable, context, replace_value);
+ CallStub(tostring_callable, context, replace_value);
+
+ // ToString(replaceValue) could potentially change the shape of the RegExp
+ // object. Recheck that we are still on the fast path and bail to runtime
+ // otherwise.
+ {
+ Label next(this);
+ BranchIfFastRegExp(context, regexp, LoadMap(regexp), &next, &runtime);
+ Bind(&next);
+ }
- Node* const dollar_char = a->IntPtrConstant('$');
- Node* const smi_minusone = a->SmiConstant(Smi::FromInt(-1));
- a->GotoUnless(a->SmiEqual(a->StringIndexOfChar(context, replace_string,
- dollar_char, int_zero),
- smi_minusone),
- &runtime);
+ Callable indexof_callable = CodeFactory::StringIndexOf(isolate());
+ Node* const dollar_string = HeapConstant(
+ isolate()->factory()->LookupSingleCharacterStringFromCode('$'));
+ Node* const dollar_ix = CallStub(indexof_callable, context, replace_string,
+ dollar_string, SmiConstant(0));
+ GotoIfNot(SmiEqual(dollar_ix, SmiConstant(-1)), &runtime);
- a->Return(ReplaceSimpleStringFastPath(a, context, regexp, string,
- replace_string));
+ Return(
+ ReplaceSimpleStringFastPath(context, regexp, string, replace_string));
}
// {regexp} is unmodified and {replace_value} is callable.
- a->Bind(&if_iscallable);
+ Bind(&if_iscallable);
{
- Node* const replace_callable = replace_value;
+ Node* const replace_fn = replace_value;
// Check if the {regexp} is global.
- Label if_isglobal(a), if_isnotglobal(a);
- Node* const is_global = FastFlagGetter(a, regexp, JSRegExp::kGlobal);
- a->Branch(is_global, &if_isglobal, &if_isnotglobal);
+ Label if_isglobal(this), if_isnotglobal(this);
- a->Bind(&if_isglobal);
- {
- Node* const result = ReplaceGlobalCallableFastPath(
- a, context, regexp, string, replace_callable);
- a->Return(result);
- }
+ Node* const is_global = FastFlagGetter(regexp, JSRegExp::kGlobal);
+ Branch(is_global, &if_isglobal, &if_isnotglobal);
- a->Bind(&if_isnotglobal);
- {
- Node* const result =
- a->CallRuntime(Runtime::kStringReplaceNonGlobalRegExpWithFunction,
- context, string, regexp, replace_callable);
- a->Return(result);
- }
- }
+ Bind(&if_isglobal);
+ Return(ReplaceGlobalCallableFastPath(context, regexp, string, replace_fn));
- a->Bind(&runtime);
- {
- Node* const result = a->CallRuntime(Runtime::kRegExpReplace, context,
- receiver, string, replace_value);
- a->Return(result);
+ Bind(&if_isnotglobal);
+ Return(CallRuntime(Runtime::kStringReplaceNonGlobalRegExpWithFunction,
+ context, string, regexp, replace_fn));
}
+
+ Bind(&runtime);
+ Return(CallRuntime(Runtime::kRegExpReplace, context, regexp, string,
+ replace_value));
}
-// Simple string matching functionality for internal use which does not modify
-// the last match info.
-void Builtins::Generate_RegExpInternalMatch(CodeStubAssembler* a) {
- typedef CodeStubAssembler::Label Label;
- typedef compiler::Node Node;
+// ES#sec-regexp.prototype-@@replace
+// RegExp.prototype [ @@replace ] ( string, replaceValue )
+TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) {
+ Node* const maybe_receiver = Parameter(0);
+ Node* const maybe_string = Parameter(1);
+ Node* const replace_value = Parameter(2);
+ Node* const context = Parameter(5);
+
+ // RegExpPrototypeReplace is a bit of a beast - a summary of dispatch logic:
+ //
+ // if (!IsFastRegExp(receiver)) CallRuntime(RegExpReplace)
+ // if (IsCallable(replace)) {
+ // if (IsGlobal(receiver)) {
+ // // Called 'fast-path' but contains several runtime calls.
+ // ReplaceGlobalCallableFastPath()
+ // } else {
+ // CallRuntime(StringReplaceNonGlobalRegExpWithFunction)
+ // }
+ // } else {
+ // if (replace.contains("$")) {
+ // CallRuntime(RegExpReplace)
+ // } else {
+ // ReplaceSimpleStringFastPath() // Bails to runtime for global regexps.
+ // }
+ // }
- Isolate* const isolate = a->isolate();
+ // Ensure {maybe_receiver} is a JSReceiver.
+ ThrowIfNotJSReceiver(context, maybe_receiver,
+ MessageTemplate::kIncompatibleMethodReceiver,
+ "RegExp.prototype.@@replace");
+ Node* const receiver = maybe_receiver;
+
+ // Convert {maybe_string} to a String.
+ Callable tostring_callable = CodeFactory::ToString(isolate());
+ Node* const string = CallStub(tostring_callable, context, maybe_string);
+
+ // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance?
+ Label stub(this), runtime(this, Label::kDeferred);
+ BranchIfFastRegExp(context, receiver, LoadMap(receiver), &stub, &runtime);
- Node* const regexp = a->Parameter(1);
- Node* const string = a->Parameter(2);
- Node* const context = a->Parameter(5);
+ Bind(&stub);
+ Callable replace_callable = CodeFactory::RegExpReplace(isolate());
+ Return(CallStub(replace_callable, context, receiver, string, replace_value));
+
+ Bind(&runtime);
+ Return(CallRuntime(Runtime::kRegExpReplace, context, receiver, string,
+ replace_value));
+}
+
+// Simple string matching functionality for internal use which does not modify
+// the last match info.
+TF_BUILTIN(RegExpInternalMatch, RegExpBuiltinsAssembler) {
+ Node* const regexp = Parameter(1);
+ Node* const string = Parameter(2);
+ Node* const context = Parameter(5);
- Node* const null = a->NullConstant();
- Node* const smi_zero = a->SmiConstant(Smi::FromInt(0));
+ Node* const null = NullConstant();
+ Node* const smi_zero = SmiConstant(Smi::FromInt(0));
- Node* const native_context = a->LoadNativeContext(context);
- Node* const internal_match_info = a->LoadContextElement(
+ Node* const native_context = LoadNativeContext(context);
+ Node* const internal_match_info = LoadContextElement(
native_context, Context::REGEXP_INTERNAL_MATCH_INFO_INDEX);
- Callable exec_callable = CodeFactory::RegExpExec(isolate);
- Node* const match_indices = a->CallStub(
- exec_callable, context, regexp, string, smi_zero, internal_match_info);
+ Callable exec_callable = CodeFactory::RegExpExec(isolate());
+ Node* const match_indices = CallStub(exec_callable, context, regexp, string,
+ smi_zero, internal_match_info);
- Label if_matched(a), if_didnotmatch(a);
- a->Branch(a->WordEqual(match_indices, null), &if_didnotmatch, &if_matched);
+ Label if_matched(this), if_didnotmatch(this);
+ Branch(WordEqual(match_indices, null), &if_didnotmatch, &if_matched);
- a->Bind(&if_didnotmatch);
- a->Return(null);
+ Bind(&if_didnotmatch);
+ Return(null);
- a->Bind(&if_matched);
+ Bind(&if_matched);
{
- Node* result = ConstructNewResultFromMatchInfo(isolate, a, context,
- match_indices, string);
- a->Return(result);
+ Node* result =
+ ConstructNewResultFromMatchInfo(context, regexp, match_indices, string);
+ Return(result);
}
}