Chromium Code Reviews| Index: src/builtins/builtins-regexp.cc |
| diff --git a/src/builtins/builtins-regexp.cc b/src/builtins/builtins-regexp.cc |
| index 371221fa70a8d0d49c83e2c38bd2e37ddbc77668..554887b7dda31ba1d34dc1af0358ddf2b0dd3b2e 100644 |
| --- a/src/builtins/builtins-regexp.cc |
| +++ b/src/builtins/builtins-regexp.cc |
| @@ -437,5 +437,476 @@ void Builtins::Generate_RegExpPrototypeExec(CodeStubAssembler* a) { |
| } |
| } |
| +namespace { |
| + |
| +compiler::Node* ThrowIfNotJSReceiver(CodeStubAssembler* a, Isolate* isolate, |
| + compiler::Node* context, |
| + compiler::Node* value, |
| + char const* method_name) { |
| + typedef compiler::Node Node; |
| + typedef CodeStubAssembler::Label Label; |
| + typedef CodeStubAssembler::Variable Variable; |
| + |
| + Label out(a), throw_exception(a, Label::kDeferred); |
| + Variable var_value_map(a, MachineRepresentation::kTagged); |
| + |
| + a->GotoIf(a->WordIsSmi(value), &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()); |
| + |
| + a->Branch(a->IsJSReceiverInstanceType(value_instance_type), &out, |
| + &throw_exception); |
| + |
| + // The {value} is not a compatible receiver for this method. |
| + a->Bind(&throw_exception); |
| + { |
| + Node* const message_id = |
| + a->SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonObject)); |
| + Node* const method_name_str = a->HeapConstant( |
| + isolate->factory()->NewStringFromAsciiChecked(method_name, TENURED)); |
| + |
| + Callable callable = CodeFactory::ToString(isolate); |
| + Node* const value_str = a->CallStub(callable, context, value); |
| + |
| + a->CallRuntime(Runtime::kThrowTypeError, context, message_id, |
| + method_name_str, value_str); |
| + var_value_map.Bind(a->UndefinedConstant()); |
| + a->Goto(&out); // Never reached. |
| + } |
| + |
| + a->Bind(&out); |
| + return var_value_map.value(); |
| +} |
| + |
| +compiler::Node* IsInitialRegExpMap(CodeStubAssembler* a, |
| + compiler::Node* context, |
| + compiler::Node* map) { |
| + typedef compiler::Node Node; |
| + |
| + 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(map, initial_map); |
| + |
| + return has_initialmap; |
| +} |
| + |
| +} // namespace |
| + |
| +void Builtins::Generate_RegExpPrototypeFlagsGetter(CodeStubAssembler* a) { |
| + typedef CodeStubAssembler::Variable Variable; |
| + typedef CodeStubAssembler::Label Label; |
| + typedef compiler::Node Node; |
| + |
| + Node* const receiver = a->Parameter(0); |
| + Node* const context = a->Parameter(3); |
| + |
| + Isolate* isolate = a->isolate(); |
| + Node* const int_zero = a->IntPtrConstant(0); |
| + Node* const int_one = a->IntPtrConstant(1); |
| + |
| + Node* const map = ThrowIfNotJSReceiver(a, isolate, context, receiver, |
|
Benedikt Meurer
2016/10/05 16:39:23
This could be more efficient if we first check for
jgruber
2016/10/06 12:34:34
Ack. I'll add this to my backlog and fix all usage
|
| + "RegExp.prototype.flags"); |
| + |
| + Variable var_length(a, MachineType::PointerRepresentation()); |
| + Variable var_flags(a, MachineType::PointerRepresentation()); |
| + |
| + // First, count the number of characters we will need and check which flags |
| + // are set. |
| + |
| + var_length.Bind(int_zero); |
| + |
| + Label if_isunmodifiedjsregexp(a), |
| + if_isnotunmodifiedjsregexp(a, Label::kDeferred); |
| + a->Branch(IsInitialRegExpMap(a, context, map), &if_isunmodifiedjsregexp, |
| + &if_isnotunmodifiedjsregexp); |
| + |
| + Label construct_string(a); |
| + a->Bind(&if_isunmodifiedjsregexp); |
| + { |
| + // 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); |
| + 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); \ |
| + } 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); |
| +#undef CASE_FOR_FLAG |
| + } |
| + |
| + 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); |
| + |
| +#define CASE_FOR_FLAG(NAME, FLAG, LABEL, NEXT_LABEL) \ |
| + do { \ |
| + a->Bind(&LABEL); \ |
| + 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); \ |
| + } 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); |
| +#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 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); \ |
| + } 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); |
| +#undef CASE_FOR_FLAG |
| + |
| + a->Bind(&out); |
| + a->Return(result); |
| + } |
| +} |
| + |
| +// ES6 21.2.5.10. |
| +BUILTIN(RegExpPrototypeSourceGetter) { |
| + HandleScope scope(isolate); |
| + |
| + 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("(?:)"); |
| + } |
| + THROW_NEW_ERROR_RETURN_FAILURE( |
| + isolate, NewTypeError(MessageTemplate::kRegExpNonRegExp, |
| + isolate->factory()->NewStringFromAsciiChecked( |
| + "RegExp.prototype.source"))); |
| + } |
| + |
| + Handle<JSRegExp> regexp = Handle<JSRegExp>::cast(recv); |
| + return regexp->source(); |
| +} |
| + |
| +// ES6 21.2.4.2. |
| +BUILTIN(RegExpPrototypeSpeciesGetter) { |
| + HandleScope scope(isolate); |
| + return *args.receiver(); |
| +} |
| + |
| +namespace { |
| + |
| +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* const receiver = a->Parameter(0); |
| + Node* const context = a->Parameter(3); |
| + |
| + Isolate* isolate = a->isolate(); |
| + Node* const int_zero = a->IntPtrConstant(0); |
| + |
| + // Check whether we have an unmodified regexp instance. |
| + Label if_isunmodifiedjsregexp(a), |
| + if_isnotunmodifiedjsregexp(a, Label::kDeferred); |
| + |
| + a->GotoIf(a->WordIsSmi(receiver), &if_isnotunmodifiedjsregexp); |
| + |
| + Node* const receiver_map = a->LoadMap(receiver); |
| + Node* const instance_type = a->LoadMapInstanceType(receiver_map); |
| + |
| + a->Branch(a->Word32Equal(instance_type, a->Int32Constant(JS_REGEXP_TYPE)), |
| + &if_isunmodifiedjsregexp, &if_isnotunmodifiedjsregexp); |
| + |
| + a->Bind(&if_isunmodifiedjsregexp); |
| + { |
| + // 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 mask = a->IntPtrConstant(flag); |
| + Node* const is_global = |
| + a->WordNotEqual(a->WordAnd(flags_intptr, mask), int_zero); |
| + a->Return(a->Select(is_global, a->TrueConstant(), a->FalseConstant())); |
| + } |
| + |
| + a->Bind(&if_isnotunmodifiedjsregexp); |
| + { |
| + 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 initial_prototype = a->LoadMapPrototype(initial_map); |
| + |
| + Label if_isprototype(a), if_isnotprototype(a); |
| + a->Branch(a->WordEqual(receiver, initial_prototype), &if_isprototype, |
| + &if_isnotprototype); |
| + |
| + a->Bind(&if_isprototype); |
| + { |
| + Node* const counter_smi = a->SmiConstant(Smi::FromInt(counter)); |
| + a->CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi); |
| + a->Return(a->UndefinedConstant()); |
| + } |
| + |
| + a->Bind(&if_isnotprototype); |
| + { |
| + Node* const message_id = |
| + a->SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonRegExp)); |
| + Node* const method_name_str = a->HeapConstant( |
| + isolate->factory()->NewStringFromAsciiChecked(method_name)); |
| + a->CallRuntime(Runtime::kThrowTypeError, context, message_id, |
| + method_name_str); |
| + a->Return(a->UndefinedConstant()); // Never reached. |
| + } |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| +// ES6 21.2.5.4. |
| +void Builtins::Generate_RegExpPrototypeGlobalGetter(CodeStubAssembler* a) { |
| + Generate_FlagGetter(a, 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"); |
| +} |
| + |
| +// ES6 21.2.5.7. |
| +void Builtins::Generate_RegExpPrototypeMultilineGetter(CodeStubAssembler* a) { |
| + Generate_FlagGetter(a, 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"); |
| +} |
| + |
| +// ES6 21.2.5.15. |
| +void Builtins::Generate_RegExpPrototypeUnicodeGetter(CodeStubAssembler* a) { |
| + Generate_FlagGetter(a, JSRegExp::kUnicode, |
| + v8::Isolate::kRegExpPrototypeUnicodeGetter, |
| + "RegExp.prototype.unicode"); |
| +} |
| + |
| +namespace { |
| + |
| +// Constants for accessing RegExpLastMatchInfo. |
| +// TODO(jgruber): Currently, RegExpLastMatchInfo is still a JSObject maintained |
| +// and accessed from JS. This is a crutch until all RegExp logic is ported, then |
| +// we can take care of RegExpLastMatchInfo. |
| + |
| +Handle<Object> GetLastMatchField(Isolate* isolate, int index) { |
| + Handle<JSObject> last_match_info = isolate->regexp_last_match_info(); |
| + return JSReceiver::GetElement(isolate, last_match_info, index) |
| + .ToHandleChecked(); |
| +} |
| + |
| +void SetLastMatchField(Isolate* isolate, int index, Handle<Object> value) { |
| + Handle<JSObject> last_match_info = isolate->regexp_last_match_info(); |
| + JSReceiver::SetElement(isolate, last_match_info, index, value, SLOPPY) |
| + .ToHandleChecked(); |
| +} |
| + |
| +int GetLastMatchNumberOfCaptures(Isolate* isolate) { |
| + Handle<Object> obj = |
| + GetLastMatchField(isolate, RegExpImpl::kLastCaptureCount); |
| + return Handle<Smi>::cast(obj)->value(); |
| +} |
| + |
| +Handle<String> GetLastMatchSubject(Isolate* isolate) { |
| + return Handle<String>::cast( |
| + GetLastMatchField(isolate, RegExpImpl::kLastSubject)); |
| +} |
| + |
| +Handle<Object> GetLastMatchInput(Isolate* isolate) { |
| + return GetLastMatchField(isolate, RegExpImpl::kLastInput); |
| +} |
| + |
| +int GetLastMatchCapture(Isolate* isolate, int i) { |
| + Handle<Object> obj = |
| + GetLastMatchField(isolate, RegExpImpl::kFirstCapture + i); |
| + return Handle<Smi>::cast(obj)->value(); |
| +} |
| + |
| +Object* GenericCaptureGetter(Isolate* isolate, int capture) { |
| + HandleScope scope(isolate); |
| + const int index = capture * 2; |
| + if (index >= GetLastMatchNumberOfCaptures(isolate)) { |
| + return isolate->heap()->empty_string(); |
| + } |
| + |
| + const int match_start = GetLastMatchCapture(isolate, index); |
| + const int match_end = GetLastMatchCapture(isolate, index + 1); |
| + if (match_start == -1 || match_end == -1) { |
| + return isolate->heap()->empty_string(); |
| + } |
| + |
| + Handle<String> last_subject = GetLastMatchSubject(isolate); |
| + return *isolate->factory()->NewSubString(last_subject, match_start, |
| + match_end); |
| +} |
| + |
| +} // namespace |
| + |
| +// The properties $1..$9 are the first nine capturing substrings of the last |
| +// successful match, or ''. The function RegExpMakeCaptureGetter will be |
| +// called with indices from 1 to 9. |
| +#define DEFINE_CAPTURE_GETTER(i) \ |
| + BUILTIN(RegExpCapture##i##Getter) { \ |
| + HandleScope scope(isolate); \ |
| + return GenericCaptureGetter(isolate, i); \ |
| + } |
| +DEFINE_CAPTURE_GETTER(1) |
| +DEFINE_CAPTURE_GETTER(2) |
| +DEFINE_CAPTURE_GETTER(3) |
| +DEFINE_CAPTURE_GETTER(4) |
| +DEFINE_CAPTURE_GETTER(5) |
| +DEFINE_CAPTURE_GETTER(6) |
| +DEFINE_CAPTURE_GETTER(7) |
| +DEFINE_CAPTURE_GETTER(8) |
| +DEFINE_CAPTURE_GETTER(9) |
| +#undef DEFINE_CAPTURE_GETTER |
| + |
| +// The properties `input` and `$_` are aliases for each other. When this |
| +// value is set, the value it is set to is coerced to a string. |
| +// Getter and setter for the input. |
| + |
| +BUILTIN(RegExpInputGetter) { |
| + HandleScope scope(isolate); |
| + Handle<Object> obj = GetLastMatchInput(isolate); |
| + return obj->IsUndefined(isolate) ? isolate->heap()->empty_string() |
| + : String::cast(*obj); |
| +} |
| + |
| +BUILTIN(RegExpInputSetter) { |
| + HandleScope scope(isolate); |
| + Handle<Object> value = args.atOrUndefined(isolate, 1); |
| + Handle<String> str; |
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, str, |
| + Object::ToString(isolate, value)); |
| + SetLastMatchField(isolate, RegExpImpl::kLastInput, str); |
| + return isolate->heap()->undefined_value(); |
| +} |
| + |
| +// Getters for the static properties lastMatch, lastParen, leftContext, and |
| +// rightContext of the RegExp constructor. The properties are computed based |
| +// on the captures array of the last successful match and the subject string |
| +// of the last successful match. |
| +BUILTIN(RegExpLastMatchGetter) { |
| + HandleScope scope(isolate); |
| + return GenericCaptureGetter(isolate, 0); |
| +} |
| + |
| +BUILTIN(RegExpLastParenGetter) { |
| + HandleScope scope(isolate); |
| + const int length = GetLastMatchNumberOfCaptures(isolate); |
| + if (length <= 2) return isolate->heap()->empty_string(); // No captures. |
| + |
| + DCHECK_EQ(0, length % 2); |
| + const int last_capture = (length / 2) - 1; |
| + |
| + // We match the SpiderMonkey behavior: return the substring defined by the |
| + // last pair (after the first pair) of elements of the capture array even if |
| + // it is empty. |
| + return GenericCaptureGetter(isolate, last_capture); |
| +} |
| + |
| +BUILTIN(RegExpLeftContextGetter) { |
| + HandleScope scope(isolate); |
| + const int start_index = GetLastMatchCapture(isolate, 0); |
| + Handle<String> last_subject = GetLastMatchSubject(isolate); |
| + return *isolate->factory()->NewSubString(last_subject, 0, start_index); |
| +} |
| + |
| +BUILTIN(RegExpRightContextGetter) { |
| + HandleScope scope(isolate); |
| + const int start_index = GetLastMatchCapture(isolate, 1); |
| + Handle<String> last_subject = GetLastMatchSubject(isolate); |
| + const int len = last_subject->length(); |
| + return *isolate->factory()->NewSubString(last_subject, start_index, len); |
| +} |
| + |
| } // namespace internal |
| } // namespace v8 |