Commit c6b1ef0e authored by Jakob Gruber's avatar Jakob Gruber Committed by Commit Bot

[regexp] Restructure fast path check logic

Prior to this CL, the regexp fast path check is stricter than it
needs to be. For example, adding any arbitrary property on the regexp
prototype would move the execution of all regexp builtins in the same
context onto the slow path. This actually happens in the real world:
popular web frameworks commonly monkey-patch builtin prototypes to add
functionality.

The intent of this CL is to widen the fast path for regexp builtins s.t.
modifications of the prototype that do not conflict with our
requirements stay on the fast path.

This is done by extending the current fast path check with an
additional step. If checking the prototype map identity or
relevant prototype property constness fails, we now compare the actual
value of all relevant properties against the expected value. If these
match, the prototype can be considered fast.

The new step as described in the previous paragraph is part of the
permissive fast path check (BranchIfFastRegExp_Permissive). The strict
variant (BranchIfFastRegExp_Strict) is also still required by a few
spots. We should refactor these to also allow the permissive check in
follow-up work.

Bug: v8:5577,chromium:977382
Change-Id: I69b2244e68ccfbd00edf17fc326aa4b5f5d089fa
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1706056
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarClemens Hammacher <clemensh@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarPeter Marshall <petermarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62948}
parent 561e5175
......@@ -53,13 +53,13 @@ class Flags final {
}
constexpr Flags operator&(const Flags& flags) const {
return Flags(*this) &= flags;
return Flags(mask_ & flags.mask_);
}
constexpr Flags operator|(const Flags& flags) const {
return Flags(*this) |= flags;
return Flags(mask_ | flags.mask_);
}
constexpr Flags operator^(const Flags& flags) const {
return Flags(*this) ^= flags;
return Flags(mask_ ^ flags.mask_);
}
Flags& operator&=(flag_type flag) { return operator&=(Flags(flag)); }
......
......@@ -2008,7 +2008,13 @@ Cast<JSArgumentsObjectWithLength>(implicit context: Context)(o: HeapObject):
Cast<FastJSRegExp>(implicit context: Context)(o: HeapObject): FastJSRegExp
labels CastError {
if (regexp::BranchIfFastRegExp(o)) return %RawDownCast<FastJSRegExp>(o);
// TODO(jgruber): Remove or redesign this. There is no single 'fast' regexp,
// the conditions to make a regexp object fast differ based on the callsite.
// For now, run the strict variant since replace (the only current callsite)
// accesses flag getters.
if (regexp::BranchIfFastRegExp_Strict(o)) {
return %RawDownCast<FastJSRegExp>(o);
}
goto CastError;
}
......
......@@ -108,8 +108,8 @@ class BaseCollectionsAssembler : public CodeStubAssembler {
// Checks whether {collection}'s initial add/set function has been modified
// (depending on {variant}, loaded from {native_context}).
void GotoIfInitialAddFunctionModified(Variant variant,
TNode<Context> native_context,
TNode<Object> collection,
TNode<NativeContext> native_context,
TNode<HeapObject> collection,
Label* if_modified);
// Gets root index for the name of the add/set function.
......@@ -186,8 +186,8 @@ void BaseCollectionsAssembler::AddConstructorEntries(
TNode<Object> table = AllocateTable(variant, context, at_least_space_for);
StoreObjectField(collection, GetTableOffset(variant), table);
GotoIf(IsNullOrUndefined(initial_entries), &exit);
GotoIfInitialAddFunctionModified(variant, native_context, collection,
&slow_loop);
GotoIfInitialAddFunctionModified(variant, CAST(native_context),
CAST(collection), &slow_loop);
Branch(use_fast_loop.value(), &fast_loop, &slow_loop);
}
BIND(&fast_loop);
......@@ -212,8 +212,8 @@ void BaseCollectionsAssembler::AddConstructorEntries(
{
// Check that add/set function has not been modified.
Label if_not_modified(this), if_modified(this);
GotoIfInitialAddFunctionModified(variant, native_context, collection,
&if_modified);
GotoIfInitialAddFunctionModified(variant, CAST(native_context),
CAST(collection), &if_modified);
Goto(&if_not_modified);
BIND(&if_modified);
Unreachable();
......@@ -356,15 +356,38 @@ RootIndex BaseCollectionsAssembler::GetAddFunctionNameIndex(Variant variant) {
}
void BaseCollectionsAssembler::GotoIfInitialAddFunctionModified(
Variant variant, TNode<Context> native_context, TNode<Object> collection,
Label* if_modified) {
Variant variant, TNode<NativeContext> native_context,
TNode<HeapObject> collection, Label* if_modified) {
STATIC_ASSERT(JSCollection::kAddFunctionDescriptorIndex ==
JSWeakCollection::kAddFunctionDescriptorIndex);
GotoIfInitialPrototypePropertyModified(
LoadMap(CAST(collection)),
GetInitialCollectionPrototype(variant, native_context),
// TODO(jgruber): Investigate if this should also fall back to full prototype
// verification.
static constexpr PrototypeCheckAssembler::Flags flags{
PrototypeCheckAssembler::kCheckPrototypePropertyConstness};
static constexpr int kNoContextIndex = -1;
STATIC_ASSERT(
(flags & PrototypeCheckAssembler::kCheckPrototypePropertyIdentity) == 0);
using DescriptorIndexNameValue =
PrototypeCheckAssembler::DescriptorIndexNameValue;
DescriptorIndexNameValue property_to_check{
JSCollection::kAddFunctionDescriptorIndex,
GetAddFunctionNameIndex(variant), if_modified);
GetAddFunctionNameIndex(variant), kNoContextIndex};
PrototypeCheckAssembler prototype_check_assembler(
state(), flags, native_context,
GetInitialCollectionPrototype(variant, native_context),
Vector<DescriptorIndexNameValue>(&property_to_check, 1));
TNode<HeapObject> prototype = LoadMapPrototype(LoadMap(collection));
Label if_unmodified(this);
prototype_check_assembler.CheckAndBranch(prototype, &if_unmodified,
if_modified);
BIND(&if_unmodified);
}
TNode<JSObject> BaseCollectionsAssembler::AllocateJSCollection(
......
......@@ -44,10 +44,6 @@ class ObjectBuiltinsAssembler : public CodeStubAssembler {
Node* ConstructDataDescriptor(Node* context, Node* value, Node* writable,
Node* enumerable, Node* configurable);
Node* GetAccessorOrUndefined(Node* accessor, Label* if_bailout);
Node* IsSpecialReceiverMap(SloppyTNode<Map> map);
TNode<Word32T> IsStringWrapperElementsKind(TNode<Map> map);
};
class ObjectEntriesValuesBuiltinsAssembler : public ObjectBuiltinsAssembler {
......@@ -72,8 +68,6 @@ class ObjectEntriesValuesBuiltinsAssembler : public ObjectBuiltinsAssembler {
void GetOwnValuesOrEntries(TNode<Context> context, TNode<Object> maybe_object,
CollectType collect_type);
void GotoIfMapHasSlowProperties(TNode<Map> map, Label* if_slow);
TNode<JSArray> FastGetOwnValuesOrEntries(
TNode<Context> context, TNode<JSObject> object,
Label* if_call_runtime_with_fast_path, Label* if_no_properties,
......@@ -144,28 +138,6 @@ Node* ObjectBuiltinsAssembler::ConstructDataDescriptor(Node* context,
return js_desc;
}
Node* ObjectBuiltinsAssembler::IsSpecialReceiverMap(SloppyTNode<Map> map) {
CSA_SLOW_ASSERT(this, IsMap(map));
TNode<BoolT> is_special =
IsSpecialReceiverInstanceType(LoadMapInstanceType(map));
uint32_t mask =
Map::HasNamedInterceptorBit::kMask | Map::IsAccessCheckNeededBit::kMask;
USE(mask);
// Interceptors or access checks imply special receiver.
CSA_ASSERT(this,
SelectConstant<BoolT>(IsSetWord32(LoadMapBitField(map), mask),
is_special, Int32TrueConstant()));
return is_special;
}
TNode<Word32T> ObjectBuiltinsAssembler::IsStringWrapperElementsKind(
TNode<Map> map) {
Node* kind = LoadMapElementsKind(map);
return Word32Or(
Word32Equal(kind, Int32Constant(FAST_STRING_WRAPPER_ELEMENTS)),
Word32Equal(kind, Int32Constant(SLOW_STRING_WRAPPER_ELEMENTS)));
}
TNode<BoolT> ObjectEntriesValuesBuiltinsAssembler::IsPropertyEnumerable(
TNode<Uint32T> details) {
TNode<Uint32T> attributes =
......@@ -242,13 +214,6 @@ void ObjectEntriesValuesBuiltinsAssembler::GetOwnValuesOrEntries(
}
}
void ObjectEntriesValuesBuiltinsAssembler::GotoIfMapHasSlowProperties(
TNode<Map> map, Label* if_slow) {
GotoIf(IsStringWrapperElementsKind(map), if_slow);
GotoIf(IsSpecialReceiverMap(map), if_slow);
GotoIf(IsDictionaryMap(map), if_slow);
}
TNode<JSArray> ObjectEntriesValuesBuiltinsAssembler::FastGetOwnValuesOrEntries(
TNode<Context> context, TNode<JSObject> object,
Label* if_call_runtime_with_fast_path, Label* if_no_properties,
......
This diff is collapsed.
......@@ -17,11 +17,6 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
explicit RegExpBuiltinsAssembler(compiler::CodeAssemblerState* state)
: CodeStubAssembler(state) {}
void BranchIfFastRegExp(
Node* const context, Node* const object, Node* const map,
base::Optional<DescriptorIndexAndName> additional_property_to_check,
Label* const if_isunmodified, Label* const if_ismodified);
// Create and initialize a RegExp object.
TNode<Object> RegExpCreate(TNode<Context> context,
TNode<Context> native_context,
......@@ -91,22 +86,67 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
MessageTemplate msg_template,
char const* method_name);
// Analogous to BranchIfFastRegExp, for use in asserts.
TNode<BoolT> IsFastRegExp(SloppyTNode<Context> context,
SloppyTNode<Object> object);
// Fast path check logic.
//
// Are you afraid? If not, you should be.
//
// It's complicated. Fast path checks protect certain assumptions, e.g. that
// relevant properties on the regexp prototype (such as exec, @@split, global)
// are unmodified.
//
// These assumptions differ by callsite. For example, RegExpPrototypeExec
// cares whether the exec property has been modified; but it's totally fine
// to modify other prototype properties. On the other hand,
// StringPrototypeSplit does care very much whether @@split has been changed.
//
// We want to keep regexp execution on the fast path as much as possible.
// Ideally, we could simply check if the regexp prototype has been modified;
// yet common web frameworks routinely mutate it for various reasons. But most
// of these mutations should happen in a way that still allows us to remain
// on the fast path. To support this, the fast path check logic necessarily
// becomes more involved.
//
// There are multiple knobs to twiddle for regexp fast path checks. We support
// checks that completely ignore the prototype, checks that verify specific
// properties on the prototype (the caller must ensure it passes in the right
// ones), and strict checks that additionally ensure the prototype is
// unchanged (we use these when we'd have to check multiple properties we
// don't care too much about, e.g. all individual flag getters).
using DescriptorIndexNameValue =
PrototypeCheckAssembler::DescriptorIndexNameValue;
void BranchIfFastRegExp(Node* const context, Node* const object,
Label* const if_isunmodified,
Label* const if_ismodified);
void BranchIfFastRegExp(
TNode<Context> context, TNode<HeapObject> object, TNode<Map> map,
PrototypeCheckAssembler::Flags prototype_check_flags,
base::Optional<DescriptorIndexNameValue> additional_property_to_check,
Label* if_isunmodified, Label* if_ismodified);
// Strict: Does not tolerate any changes to the prototype map.
// Permissive: Allows changes to the prototype map except for the exec
// property.
void BranchIfFastRegExp_Strict(TNode<Context> context,
TNode<HeapObject> object,
Label* if_isunmodified, Label* if_ismodified);
void BranchIfFastRegExp_Permissive(TNode<Context> context,
TNode<HeapObject> object,
Label* if_isunmodified,
Label* if_ismodified);
// Analogous to BranchIfFastRegExp_Permissive, for use in asserts.
TNode<BoolT> IsFastRegExp_Permissive(SloppyTNode<Context> context,
SloppyTNode<Object> object);
// Performs fast path checks on the given object itself, but omits prototype
// checks.
Node* IsFastRegExpNoPrototype(Node* const context, Node* const object);
TNode<BoolT> IsFastRegExpWithOriginalExec(TNode<Context> context,
TNode<JSRegExp> object);
Node* IsFastRegExpNoPrototype(Node* const context, Node* const object,
Node* const map);
// For debugging only. Uses a slow GetProperty call to fetch object.exec.
TNode<BoolT> IsFastRegExpWithOriginalExec(TNode<Context> context,
TNode<JSRegExp> object);
void BranchIfFastRegExpResult(Node* const context, Node* const object,
Label* if_isunmodified, Label* if_ismodified);
......
......@@ -955,7 +955,8 @@ void StringIncludesIndexOfAssembler::Generate(SearchVariant variant,
void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol(
Node* const context, Node* const object, Node* const maybe_string,
Handle<Symbol> symbol, DescriptorIndexAndName symbol_index,
Handle<Symbol> symbol,
DescriptorIndexNameValue additional_property_to_check,
const NodeFunction0& regexp_call, const NodeFunction1& generic_call) {
Label out(this);
......@@ -972,9 +973,17 @@ void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol(
GotoIf(TaggedIsSmi(maybe_string), &slow_lookup);
GotoIfNot(IsString(maybe_string), &slow_lookup);
// Note we don't run a full (= permissive) check here, because passing the
// check implies calling the fast variants of target builtins, which assume
// we've already made their appropriate fast path checks. This is not the
// case though; e.g.: some of the target builtins access flag getters.
// TODO(jgruber): Handle slow flag accesses on the fast path and make this
// permissive.
RegExpBuiltinsAssembler regexp_asm(state());
regexp_asm.BranchIfFastRegExp(context, object, LoadMap(object),
symbol_index, &stub_call, &slow_lookup);
regexp_asm.BranchIfFastRegExp(
CAST(context), CAST(object), LoadMap(object),
PrototypeCheckAssembler::kCheckPrototypePropertyConstness,
additional_property_to_check, &stub_call, &slow_lookup);
BIND(&stub_call);
// TODO(jgruber): Add a no-JS scope once it exists.
......@@ -1073,8 +1082,9 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
MaybeCallFunctionAtSymbol(
context, search, receiver, isolate()->factory()->replace_symbol(),
DescriptorIndexAndName{JSRegExp::kSymbolReplaceFunctionDescriptorIndex,
RootIndex::kreplace_symbol},
DescriptorIndexNameValue{JSRegExp::kSymbolReplaceFunctionDescriptorIndex,
RootIndex::kreplace_symbol,
Context::REGEXP_REPLACE_FUNCTION_INDEX},
[=]() {
Return(CallBuiltin(Builtins::kRegExpReplace, context, search, receiver,
replace));
......@@ -1225,19 +1235,19 @@ class StringMatchSearchAssembler : public StringBuiltinsAssembler {
Builtins::Name builtin;
Handle<Symbol> symbol;
DescriptorIndexAndName property_to_check;
DescriptorIndexNameValue property_to_check;
if (variant == kMatch) {
builtin = Builtins::kRegExpMatchFast;
symbol = isolate()->factory()->match_symbol();
property_to_check =
DescriptorIndexAndName{JSRegExp::kSymbolMatchFunctionDescriptorIndex,
RootIndex::kmatch_symbol};
property_to_check = DescriptorIndexNameValue{
JSRegExp::kSymbolMatchFunctionDescriptorIndex,
RootIndex::kmatch_symbol, Context::REGEXP_MATCH_FUNCTION_INDEX};
} else {
builtin = Builtins::kRegExpSearchFast;
symbol = isolate()->factory()->search_symbol();
property_to_check =
DescriptorIndexAndName{JSRegExp::kSymbolSearchFunctionDescriptorIndex,
RootIndex::ksearch_symbol};
property_to_check = DescriptorIndexNameValue{
JSRegExp::kSymbolSearchFunctionDescriptorIndex,
RootIndex::ksearch_symbol, Context::REGEXP_SEARCH_FUNCTION_INDEX};
}
RequireObjectCoercible(context, receiver, method_name);
......@@ -1263,9 +1273,13 @@ class StringMatchSearchAssembler : public StringBuiltinsAssembler {
TNode<Object> regexp = regexp_asm.RegExpCreate(
context, initial_map, maybe_regexp, EmptyStringConstant());
// TODO(jgruber): Handle slow flag accesses on the fast path and make this
// permissive.
Label fast_path(this), slow_path(this);
regexp_asm.BranchIfFastRegExp(context, regexp, initial_map,
property_to_check, &fast_path, &slow_path);
regexp_asm.BranchIfFastRegExp(
context, CAST(regexp), initial_map,
PrototypeCheckAssembler::kCheckPrototypePropertyConstness,
property_to_check, &fast_path, &slow_path);
BIND(&fast_path);
Return(CallBuiltin(builtin, context, regexp, receiver_string));
......@@ -1320,8 +1334,9 @@ TF_BUILTIN(StringPrototypeMatchAll, StringBuiltinsAssembler) {
};
MaybeCallFunctionAtSymbol(
context, maybe_regexp, receiver, isolate()->factory()->match_all_symbol(),
DescriptorIndexAndName{JSRegExp::kSymbolMatchAllFunctionDescriptorIndex,
RootIndex::kmatch_all_symbol},
DescriptorIndexNameValue{JSRegExp::kSymbolMatchAllFunctionDescriptorIndex,
RootIndex::kmatch_all_symbol,
Context::REGEXP_MATCH_ALL_FUNCTION_INDEX},
if_regexp_call, if_generic_call);
RegExpMatchAllAssembler regexp_asm(state());
......@@ -1579,8 +1594,9 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
MaybeCallFunctionAtSymbol(
context, separator, receiver, isolate()->factory()->split_symbol(),
DescriptorIndexAndName{JSRegExp::kSymbolSplitFunctionDescriptorIndex,
RootIndex::ksplit_symbol},
DescriptorIndexNameValue{JSRegExp::kSymbolSplitFunctionDescriptorIndex,
RootIndex::ksplit_symbol,
Context::REGEXP_SPLIT_FUNCTION_INDEX},
[&]() {
args.PopAndReturn(CallBuiltin(Builtins::kRegExpSplit, context,
separator, receiver, limit));
......
......@@ -94,12 +94,13 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
// Important: {regexp_call} may not contain any code that can call into JS.
using NodeFunction0 = std::function<void()>;
using NodeFunction1 = std::function<void(Node* fn)>;
void MaybeCallFunctionAtSymbol(Node* const context, Node* const object,
Node* const maybe_string,
Handle<Symbol> symbol,
DescriptorIndexAndName symbol_index,
const NodeFunction0& regexp_call,
const NodeFunction1& generic_call);
using DescriptorIndexNameValue =
PrototypeCheckAssembler::DescriptorIndexNameValue;
void MaybeCallFunctionAtSymbol(
Node* const context, Node* const object, Node* const maybe_string,
Handle<Symbol> symbol,
DescriptorIndexNameValue additional_property_to_check,
const NodeFunction0& regexp_call, const NodeFunction1& generic_call);
};
class StringIncludesIndexOfAssembler : public StringBuiltinsAssembler {
......
......@@ -6,7 +6,7 @@
namespace regexp {
extern macro RegExpBuiltinsAssembler::BranchIfFastRegExp(
extern macro RegExpBuiltinsAssembler::BranchIfFastRegExp_Strict(
implicit context: Context)(HeapObject): never labels IsFast,
IsSlow;
......
......@@ -1472,6 +1472,34 @@ TNode<BoolT> CodeStubAssembler::TaggedDoesntHaveInstanceType(
[=]() { return DoesntHaveInstanceType(any_tagged, type); });
}
TNode<BoolT> CodeStubAssembler::IsSpecialReceiverMap(SloppyTNode<Map> map) {
CSA_SLOW_ASSERT(this, IsMap(map));
TNode<BoolT> is_special =
IsSpecialReceiverInstanceType(LoadMapInstanceType(map));
uint32_t mask =
Map::HasNamedInterceptorBit::kMask | Map::IsAccessCheckNeededBit::kMask;
USE(mask);
// Interceptors or access checks imply special receiver.
CSA_ASSERT(this,
SelectConstant<BoolT>(IsSetWord32(LoadMapBitField(map), mask),
is_special, Int32TrueConstant()));
return is_special;
}
TNode<Word32T> CodeStubAssembler::IsStringWrapperElementsKind(TNode<Map> map) {
Node* kind = LoadMapElementsKind(map);
return Word32Or(
Word32Equal(kind, Int32Constant(FAST_STRING_WRAPPER_ELEMENTS)),
Word32Equal(kind, Int32Constant(SLOW_STRING_WRAPPER_ELEMENTS)));
}
void CodeStubAssembler::GotoIfMapHasSlowProperties(TNode<Map> map,
Label* if_slow) {
GotoIf(IsStringWrapperElementsKind(map), if_slow);
GotoIf(IsSpecialReceiverMap(map), if_slow);
GotoIf(IsDictionaryMap(map), if_slow);
}
TNode<HeapObject> CodeStubAssembler::LoadFastProperties(
SloppyTNode<JSObject> object) {
CSA_SLOW_ASSERT(this, Word32BinaryNot(IsDictionaryMap(LoadMap(object))));
......@@ -13974,63 +14002,138 @@ void CodeStubAssembler::SetPropertyLength(TNode<Context> context,
BIND(&done);
}
void CodeStubAssembler::GotoIfInitialPrototypePropertyModified(
TNode<Map> object_map, TNode<Map> initial_prototype_map, int descriptor,
RootIndex field_name_root_index, Label* if_modified) {
DescriptorIndexAndName index_name{descriptor, field_name_root_index};
GotoIfInitialPrototypePropertiesModified(
object_map, initial_prototype_map,
Vector<DescriptorIndexAndName>(&index_name, 1), if_modified);
TNode<String> CodeStubAssembler::TaggedToDirectString(TNode<Object> value,
Label* fail) {
ToDirectStringAssembler to_direct(state(), value);
to_direct.TryToDirect(fail);
to_direct.PointerToData(fail);
return CAST(value);
}
void CodeStubAssembler::GotoIfInitialPrototypePropertiesModified(
TNode<Map> object_map, TNode<Map> initial_prototype_map,
Vector<DescriptorIndexAndName> properties, Label* if_modified) {
TNode<Map> prototype_map = LoadMap(LoadMapPrototype(object_map));
GotoIfNot(WordEqual(prototype_map, initial_prototype_map), if_modified);
// We need to make sure that relevant properties in the prototype have
// not been tampered with. We do this by checking that their slots
// in the prototype's descriptor array are still marked as const.
PrototypeCheckAssembler::PrototypeCheckAssembler(
compiler::CodeAssemblerState* state, Flags flags,
TNode<NativeContext> native_context, TNode<Map> initial_prototype_map,
Vector<DescriptorIndexNameValue> properties)
: CodeStubAssembler(state),
flags_(flags),
native_context_(native_context),
initial_prototype_map_(initial_prototype_map),
properties_(properties) {}
void PrototypeCheckAssembler::CheckAndBranch(TNode<HeapObject> prototype,
Label* if_unmodified,
Label* if_modified) {
TNode<Map> prototype_map = LoadMap(prototype);
TNode<DescriptorArray> descriptors = LoadMapDescriptors(prototype_map);
TNode<Uint32T> combined_details;
for (int i = 0; i < properties.length(); i++) {
// Assert the descriptor index is in-bounds.
int descriptor = properties[i].descriptor_index;
CSA_ASSERT(this, Int32LessThan(Int32Constant(descriptor),
LoadNumberOfDescriptors(descriptors)));
// Assert that the name is correct. This essentially checks that
// the descriptor index corresponds to the insertion order in
// the bootstrapper.
CSA_ASSERT(this,
WordEqual(LoadKeyByDescriptorEntry(descriptors, descriptor),
LoadRoot(properties[i].name_root_index)));
TNode<Uint32T> details =
DescriptorArrayGetDetails(descriptors, Uint32Constant(descriptor));
if (i == 0) {
combined_details = details;
} else {
combined_details = Word32And(combined_details, details);
// The continuation of a failed fast check: if property identity checks are
// enabled, we continue there (since they may still classify the prototype as
// fast), otherwise we bail out.
Label property_identity_check(this, Label::kDeferred);
Label* if_fast_check_failed =
((flags_ & kCheckPrototypePropertyIdentity) == 0)
? if_modified
: &property_identity_check;
if ((flags_ & kCheckPrototypePropertyConstness) != 0) {
// A simple prototype map identity check. Note that map identity does not
// guarantee unmodified properties. It does guarantee that no new properties
// have been added, or old properties deleted.
GotoIfNot(WordEqual(prototype_map, initial_prototype_map_),
if_fast_check_failed);
// We need to make sure that relevant properties in the prototype have
// not been tampered with. We do this by checking that their slots
// in the prototype's descriptor array are still marked as const.
TNode<Uint32T> combined_details;
for (int i = 0; i < properties_.length(); i++) {
// Assert the descriptor index is in-bounds.
int descriptor = properties_[i].descriptor_index;
CSA_ASSERT(this, Int32LessThan(Int32Constant(descriptor),
LoadNumberOfDescriptors(descriptors)));
// Assert that the name is correct. This essentially checks that
// the descriptor index corresponds to the insertion order in
// the bootstrapper.
CSA_ASSERT(this,
WordEqual(LoadKeyByDescriptorEntry(descriptors, descriptor),
LoadRoot(properties_[i].name_root_index)));
TNode<Uint32T> details =
DescriptorArrayGetDetails(descriptors, Uint32Constant(descriptor));
if (i == 0) {
combined_details = details;
} else {
combined_details = Word32And(combined_details, details);
}
}
TNode<Uint32T> constness =
DecodeWord32<PropertyDetails::ConstnessField>(combined_details);
Branch(
Word32Equal(constness,
Int32Constant(static_cast<int>(PropertyConstness::kConst))),
if_unmodified, if_fast_check_failed);
}
TNode<Uint32T> constness =
DecodeWord32<PropertyDetails::ConstnessField>(combined_details);
if ((flags_ & kCheckPrototypePropertyIdentity) != 0) {
// The above checks have failed, for whatever reason (maybe the prototype
// map has changed, or a property is no longer const). This block implements
// a more thorough check that can also accept maps which 1. do not have the
// initial map, 2. have mutable relevant properties, but 3. still match the
// expected value for all relevant properties.
GotoIfNot(
Word32Equal(constness,
Int32Constant(static_cast<int>(PropertyConstness::kConst))),
if_modified);
}
BIND(&property_identity_check);
TNode<String> CodeStubAssembler::TaggedToDirectString(TNode<Object> value,
Label* fail) {
ToDirectStringAssembler to_direct(state(), value);
to_direct.TryToDirect(fail);
to_direct.PointerToData(fail);
return CAST(value);
int max_descriptor_index = -1;
for (int i = 0; i < properties_.length(); i++) {
max_descriptor_index =
std::max(max_descriptor_index, properties_[i].descriptor_index);
}
// If the greatest descriptor index is out of bounds, the map cannot be
// fast.
GotoIfNot(Int32LessThan(Int32Constant(max_descriptor_index),
LoadNumberOfDescriptors(descriptors)),
if_modified);
// Logic below only handles maps with fast properties.
GotoIfMapHasSlowProperties(prototype_map, if_modified);
for (int i = 0; i < properties_.length(); i++) {
const DescriptorIndexNameValue& p = properties_[i];
const int descriptor = p.descriptor_index;
// Check if the name is correct. This essentially checks that
// the descriptor index corresponds to the insertion order in
// the bootstrapper.
GotoIfNot(WordEqual(LoadKeyByDescriptorEntry(descriptors, descriptor),
LoadRoot(p.name_root_index)),
if_modified);
// Finally, check whether the actual value equals the expected value.
TNode<Uint32T> details =
DescriptorArrayGetDetails(descriptors, Uint32Constant(descriptor));
TVARIABLE(Uint32T, var_details, details);
TVARIABLE(Object, var_value);
const int key_index = DescriptorArray::ToKeyIndex(descriptor);
LoadPropertyFromFastObject(prototype, prototype_map, descriptors,
IntPtrConstant(key_index), &var_details,
&var_value);
TNode<Object> actual_value = var_value.value();
TNode<Object> expected_value =
LoadContextElement(native_context_, p.expected_value_context_index);
GotoIfNot(WordEqual(actual_value, expected_value), if_modified);
}
Goto(if_unmodified);
}
}
} // namespace internal
......
......@@ -916,6 +916,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
InstanceType type);
TNode<BoolT> TaggedDoesntHaveInstanceType(SloppyTNode<HeapObject> any_tagged,
InstanceType type);
TNode<Word32T> IsStringWrapperElementsKind(TNode<Map> map);
void GotoIfMapHasSlowProperties(TNode<Map> map, Label* if_slow);
// Load the properties backing store of a JSObject.
TNode<HeapObject> LoadSlowProperties(SloppyTNode<JSObject> object);
TNode<HeapObject> LoadFastProperties(SloppyTNode<JSObject> object);
......@@ -3374,34 +3378,6 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
void SetPropertyLength(TNode<Context> context, TNode<Object> array,
TNode<Number> length);
// Checks that {object_map}'s prototype map is the {initial_prototype_map} and
// makes sure that the field with name at index {descriptor} is still
// constant. If it is not, go to label {if_modified}.
//
// To make the checks robust, the method also asserts that the descriptor has
// the right key, the caller must pass the root index of the key
// in {field_name_root_index}.
//
// This is useful for checking that given function has not been patched
// on the prototype.
void GotoIfInitialPrototypePropertyModified(TNode<Map> object_map,
TNode<Map> initial_prototype_map,
int descfriptor,
RootIndex field_name_root_index,
Label* if_modified);
struct DescriptorIndexAndName {
DescriptorIndexAndName() {}
DescriptorIndexAndName(int descriptor_index, RootIndex name_root_index)
: descriptor_index(descriptor_index),
name_root_index(name_root_index) {}
int descriptor_index;
RootIndex name_root_index;
};
void GotoIfInitialPrototypePropertiesModified(
TNode<Map> object_map, TNode<Map> initial_prototype_map,
Vector<DescriptorIndexAndName> properties, Label* if_modified);
// Implements DescriptorArray::Search().
void DescriptorLookup(SloppyTNode<Name> unique_name,
SloppyTNode<DescriptorArray> descriptors,
......@@ -3766,6 +3742,43 @@ class ToDirectStringAssembler : public CodeStubAssembler {
const Flags flags_;
};
// Performs checks on a given prototype (e.g. map identity, property
// verification), intended for use in fast path checks.
class PrototypeCheckAssembler : public CodeStubAssembler {
public:
enum Flag {
kCheckPrototypePropertyConstness = 1 << 0,
kCheckPrototypePropertyIdentity = 1 << 1,
kCheckFull =
kCheckPrototypePropertyConstness | kCheckPrototypePropertyIdentity,
};
using Flags = base::Flags<Flag>;
// A tuple describing a relevant property. It contains the descriptor index of
// the property (within the descriptor array), the property's expected name
// (stored as a root), and the property's expected value (stored on the native
// context).
struct DescriptorIndexNameValue {
int descriptor_index;
RootIndex name_root_index;
int expected_value_context_index;
};
PrototypeCheckAssembler(compiler::CodeAssemblerState* state, Flags flags,
TNode<NativeContext> native_context,
TNode<Map> initial_prototype_map,
Vector<DescriptorIndexNameValue> properties);
void CheckAndBranch(TNode<HeapObject> prototype, Label* if_unmodified,
Label* if_modified);
private:
const Flags flags_;
const TNode<NativeContext> native_context_;
const TNode<Map> initial_prototype_map_;
const Vector<DescriptorIndexNameValue> properties_;
};
DEFINE_OPERATORS_FOR_FLAGS(CodeStubAssembler::AllocationFlags)
} // namespace internal
......
......@@ -2450,11 +2450,9 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Handle<JSFunction> fun =
SimpleInstallFunction(isolate_, prototype, "exec",
Builtins::kRegExpPrototypeExec, 1, true);
// Check that index of "exec" function in JSRegExp is correct.
native_context()->set_regexp_exec_function(*fun);
DCHECK_EQ(JSRegExp::kExecFunctionDescriptorIndex,
prototype->map().LastAdded());
native_context()->set_regexp_exec_function(*fun);
}
SimpleInstallGetter(isolate_, prototype, factory->dotAll_string(),
......@@ -2481,35 +2479,50 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
SimpleInstallFunction(isolate_, prototype, "test",
Builtins::kRegExpPrototypeTest, 1, true);
InstallFunctionAtSymbol(isolate_, prototype, factory->match_symbol(),
"[Symbol.match]", Builtins::kRegExpPrototypeMatch,
1, true);
DCHECK_EQ(JSRegExp::kSymbolMatchFunctionDescriptorIndex,
prototype->map().LastAdded());
InstallFunctionAtSymbol(isolate_, prototype, factory->match_all_symbol(),
"[Symbol.matchAll]",
Builtins::kRegExpPrototypeMatchAll, 1, true);
DCHECK_EQ(JSRegExp::kSymbolMatchAllFunctionDescriptorIndex,
prototype->map().LastAdded());
InstallFunctionAtSymbol(isolate_, prototype, factory->replace_symbol(),
"[Symbol.replace]",
Builtins::kRegExpPrototypeReplace, 2, false);
DCHECK_EQ(JSRegExp::kSymbolReplaceFunctionDescriptorIndex,
prototype->map().LastAdded());
InstallFunctionAtSymbol(isolate_, prototype, factory->search_symbol(),
"[Symbol.search]",
Builtins::kRegExpPrototypeSearch, 1, true);
DCHECK_EQ(JSRegExp::kSymbolSearchFunctionDescriptorIndex,
prototype->map().LastAdded());
InstallFunctionAtSymbol(isolate_, prototype, factory->split_symbol(),
"[Symbol.split]", Builtins::kRegExpPrototypeSplit,
2, false);
DCHECK_EQ(JSRegExp::kSymbolSplitFunctionDescriptorIndex,
prototype->map().LastAdded());
{
Handle<JSFunction> fun = InstallFunctionAtSymbol(
isolate_, prototype, factory->match_symbol(), "[Symbol.match]",
Builtins::kRegExpPrototypeMatch, 1, true);
native_context()->set_regexp_match_function(*fun);
DCHECK_EQ(JSRegExp::kSymbolMatchFunctionDescriptorIndex,
prototype->map().LastAdded());
}
{
Handle<JSFunction> fun = InstallFunctionAtSymbol(
isolate_, prototype, factory->match_all_symbol(),
"[Symbol.matchAll]", Builtins::kRegExpPrototypeMatchAll, 1, true);
native_context()->set_regexp_match_all_function(*fun);
DCHECK_EQ(JSRegExp::kSymbolMatchAllFunctionDescriptorIndex,
prototype->map().LastAdded());
}
{
Handle<JSFunction> fun = InstallFunctionAtSymbol(
isolate_, prototype, factory->replace_symbol(), "[Symbol.replace]",
Builtins::kRegExpPrototypeReplace, 2, false);
native_context()->set_regexp_replace_function(*fun);
DCHECK_EQ(JSRegExp::kSymbolReplaceFunctionDescriptorIndex,
prototype->map().LastAdded());
}
{
Handle<JSFunction> fun = InstallFunctionAtSymbol(
isolate_, prototype, factory->search_symbol(), "[Symbol.search]",
Builtins::kRegExpPrototypeSearch, 1, true);
native_context()->set_regexp_search_function(*fun);
DCHECK_EQ(JSRegExp::kSymbolSearchFunctionDescriptorIndex,
prototype->map().LastAdded());
}
{
Handle<JSFunction> fun = InstallFunctionAtSymbol(
isolate_, prototype, factory->split_symbol(), "[Symbol.split]",
Builtins::kRegExpPrototypeSplit, 2, false);
native_context()->set_regexp_split_function(*fun);
DCHECK_EQ(JSRegExp::kSymbolSplitFunctionDescriptorIndex,
prototype->map().LastAdded());
}
Handle<Map> prototype_map(prototype->map(), isolate());
Map::SetShouldBeFastPrototypeMap(prototype_map, true, isolate_);
......
......@@ -227,10 +227,15 @@ enum ContextLookupFlags {
V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \
V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \
V(REGEXP_LAST_MATCH_INFO_INDEX, RegExpMatchInfo, regexp_last_match_info) \
V(REGEXP_MATCH_ALL_FUNCTION_INDEX, JSFunction, regexp_match_all_function) \
V(REGEXP_MATCH_FUNCTION_INDEX, JSFunction, regexp_match_function) \
V(REGEXP_PROTOTYPE_INDEX, JSObject, regexp_prototype) \
V(REGEXP_PROTOTYPE_MAP_INDEX, Map, regexp_prototype_map) \
V(REGEXP_REPLACE_FUNCTION_INDEX, JSFunction, regexp_replace_function) \
V(REGEXP_RESULT_MAP_INDEX, Map, regexp_result_map) \
V(REGEXP_SEARCH_FUNCTION_INDEX, JSFunction, regexp_search_function) \
V(REGEXP_SPECIES_PROTECTOR_INDEX, PropertyCell, regexp_species_protector) \
V(REGEXP_SPLIT_FUNCTION_INDEX, JSFunction, regexp_split_function) \
V(INITIAL_REGEXP_STRING_ITERATOR_PROTOTYPE_MAP_INDEX, Map, \
initial_regexp_string_iterator_prototype_map) \
V(SCRIPT_CONTEXT_TABLE_INDEX, ScriptContextTable, script_context_table) \
......
......@@ -179,6 +179,12 @@ bool RegExpUtils::IsUnmodifiedRegExp(Isolate* isolate, Handle<Object> obj) {
return false;
}
// Note: Unlike the more involved check in CSA (see BranchIfFastRegExp), this
// does not go on to check the actual value of the exec property. This would
// not be valid since this method is called from places that access the flags
// property. Similar spots in CSA would use BranchIfFastRegExp_Strict in this
// case.
if (!isolate->IsRegExpSpeciesLookupChainIntact(isolate->native_context())) {
return false;
}
......
......@@ -38,6 +38,9 @@ class RegExpUtils : public AllStatic {
// Checks whether the given object is an unmodified JSRegExp instance.
// Neither the object's map, nor its prototype's map, nor any relevant
// method on the prototype may be modified.
//
// Note: This check is limited may only be used in situations where the only
// relevant property is 'exec'.
static bool IsUnmodifiedRegExp(Isolate* isolate, Handle<Object> obj);
// ES#sec-advancestringindex
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment