Commit 4d39e342 authored by Georg Neis's avatar Georg Neis Committed by Commit Bot

[turbofan] Don't access heap in ReduceElementAccess

This CL builds on top of feedback preprocessing. It brokerizes
all parts of element access reduction and disallows heap access there
(except for debug tracing).

To make this work without breaking tests (when concurrent inlining is
enabled):
- We don't inline functions that weren't serialized for compilation.
- We don't optimize for constant typed-array receivers when the typed
  array wasn't serialized.

This means that from now on --concurrent-inlining (and thus --future)
may result in less optimization than the default configuration.

Bug: v8:7790
Change-Id: I22685258b7d841fc9183bf99775d3f09cd272927
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1495556
Commit-Queue: Georg Neis <neis@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60061}
parent f044f91d
......@@ -8,6 +8,7 @@
#include <stdint.h>
#include "src/base/macros.h"
#include "src/base/optional.h"
#include "src/globals.h"
#include "src/pointer-with-payload.h"
......@@ -167,10 +168,20 @@ typedef PerThreadAssertScopeDebugOnly<CODE_DEPENDENCY_CHANGE_ASSERT, true>
AllowCodeDependencyChange;
class DisallowHeapAccess {
DisallowHeapAllocation no_heap_allocation_;
DisallowCodeDependencyChange no_dependency_change_;
DisallowHandleAllocation no_handle_allocation_;
DisallowHandleDereference no_handle_dereference_;
DisallowCodeDependencyChange no_dependency_change_;
DisallowHeapAllocation no_heap_allocation_;
};
class DisallowHeapAccessIf {
public:
explicit DisallowHeapAccessIf(bool condition) {
if (condition) maybe_disallow_.emplace();
}
private:
base::Optional<DisallowHeapAccess> maybe_disallow_;
};
// Per-isolate assert scopes.
......
......@@ -249,8 +249,9 @@ bool AccessInfoFactory::ComputeElementAccessInfo(
Handle<Map> map, AccessMode access_mode,
ElementAccessInfo* access_info) const {
// Check if it is safe to inline element access for the {map}.
if (!CanInlineElementAccess(map)) return false;
ElementsKind const elements_kind = map->elements_kind();
MapRef map_ref(broker(), map);
if (!CanInlineElementAccess(map_ref)) return false;
ElementsKind const elements_kind = map_ref.elements_kind();
*access_info = ElementAccessInfo(MapHandles{map}, elements_kind);
return true;
}
......@@ -259,30 +260,34 @@ bool AccessInfoFactory::ComputeElementAccessInfos(
FeedbackNexus nexus, MapHandles const& maps, AccessMode access_mode,
ZoneVector<ElementAccessInfo>* access_infos) const {
ProcessedFeedback processed(broker()->zone());
ProcessFeedbackMapsForElementAccess(isolate(), maps, &processed);
if (FLAG_concurrent_inlining) {
if (broker()->HasFeedback(nexus)) {
// We have already processed the feedback for this nexus during
// serialization. Use that data instead of the data computed above.
ProcessedFeedback const& preprocessed =
broker()->GetOrCreateFeedback(nexus);
TRACE_BROKER(broker(),
"ComputeElementAccessInfos: using preprocessed feedback "
<< "(slot " << nexus.slot() << " of "
<< Brief(*nexus.vector_handle()) << "; "
<< preprocessed.receiver_maps.size() << "/"
<< preprocessed.transitions.size() << " vs "
<< processed.receiver_maps.size() << "/"
<< processed.transitions.size() << ").\n");
processed.receiver_maps = preprocessed.receiver_maps;
processed.transitions = preprocessed.transitions;
} else {
TRACE_BROKER(broker(),
"ComputeElementAccessInfos: missing preprocessed feedback "
<< "(slot " << nexus.slot() << " of "
<< Brief(*nexus.vector_handle()) << ").\n");
}
// TODO(neis): When concurrent inlining is ready,
// - change the printing below to not look into the heap,
// - remove the call to ProcessFeedbackMapsForElementAccess,
// - remove the Allow* scopes,
AllowCodeDependencyChange dependency_change_;
AllowHandleAllocation handle_allocation_;
AllowHandleDereference handle_dereference_;
AllowHeapAllocation heap_allocation_;
// We have already processed the feedback for this nexus during
// serialization. Use that data! We still process the incoming {maps} (even
// though we don't use them) so that we can print a comparison.
ProcessFeedbackMapsForElementAccess(broker(), maps, &processed);
ProcessedFeedback const& preprocessed = broker()->GetFeedback(nexus);
TRACE_BROKER(broker(),
"ComputeElementAccessInfos: using preprocessed feedback "
<< "(slot " << nexus.slot() << " of "
<< Brief(*nexus.vector_handle()) << "; "
<< preprocessed.receiver_maps.size() << "/"
<< preprocessed.transitions.size() << " vs "
<< processed.receiver_maps.size() << "/"
<< processed.transitions.size() << ").\n");
processed.receiver_maps = preprocessed.receiver_maps;
processed.transitions = preprocessed.transitions;
} else {
ProcessFeedbackMapsForElementAccess(broker(), maps, &processed);
}
if (processed.receiver_maps.empty()) return false;
......@@ -308,7 +313,7 @@ bool AccessInfoFactory::ComputeElementAccessInfos(
// Collect the possible transitions for the {receiver_map}.
for (auto transition : processed.transitions) {
if (transition.second.is_identical_to(receiver_map)) {
if (transition.second.equals(receiver_map)) {
access_info.AddTransitionSource(transition.first);
}
}
......@@ -619,36 +624,22 @@ Maybe<ElementsKind> GeneralizeElementsKind(ElementsKind this_kind,
bool AccessInfoFactory::ConsolidateElementLoad(
ProcessedFeedback const& processed, ElementAccessInfo* access_info) const {
CHECK(!processed.receiver_maps.empty());
// We want to look at each map but the maps are split across
// {processed.receiver_maps} and {processed.transitions}.
InstanceType instance_type = processed.receiver_maps.front()->instance_type();
ElementsKind elements_kind = processed.receiver_maps.front()->elements_kind();
auto processMap = [&](Handle<Map> map) {
if (!CanInlineElementAccess(map) || map->instance_type() != instance_type) {
ProcessedFeedback::MapIterator it = processed.all_maps(broker());
MapRef first_map = it.current();
InstanceType instance_type = first_map.instance_type();
ElementsKind elements_kind = first_map.elements_kind();
MapHandles maps;
for (; !it.done(); it.advance()) {
MapRef map = it.current();
if (map.instance_type() != instance_type || !CanInlineElementAccess(map)) {
return false;
}
if (!GeneralizeElementsKind(elements_kind, map->elements_kind())
if (!GeneralizeElementsKind(elements_kind, map.elements_kind())
.To(&elements_kind)) {
return false;
}
return true;
};
for (Handle<Map> map : processed.receiver_maps) {
if (!processMap(map)) return false;
}
MapHandles maps(processed.receiver_maps.begin(),
processed.receiver_maps.end());
for (auto& pair : processed.transitions) {
if (!processMap(pair.first) || !processMap(pair.second)) return false;
maps.push_back(pair.first);
maps.push_back(pair.second);
maps.push_back(map.object());
}
// {maps} may now contain duplicate entries, but that shouldn't matter.
*access_info = ElementAccessInfo(maps, elements_kind);
return true;
......
......@@ -4,6 +4,10 @@
#include "src/compiler/js-heap-broker.h"
#ifdef ENABLE_SLOW_DCHECKS
#include <algorithm>
#endif
#include "src/ast/modules.h"
#include "src/bootstrapper.h"
#include "src/boxed-float.h"
......@@ -241,6 +245,7 @@ class JSTypedArrayData : public JSObjectData {
void* elements_external_pointer() const { return elements_external_pointer_; }
void Serialize(JSHeapBroker* broker);
bool serialized() const { return serialized_; }
HeapObjectData* buffer() const { return buffer_; }
......@@ -707,6 +712,10 @@ class MapData : public HeapObjectData {
return prototype_;
}
void SerializeForElementLoad(JSHeapBroker* broker);
void SerializeForElementStore(JSHeapBroker* broker);
private:
InstanceType const instance_type_;
int const instance_size_;
......@@ -734,6 +743,10 @@ class MapData : public HeapObjectData {
bool serialized_prototype_ = false;
ObjectData* prototype_ = nullptr;
bool serialized_for_element_load_ = false;
bool serialized_for_element_store_ = false;
};
AllocationSiteData::AllocationSiteData(JSHeapBroker* broker,
......@@ -1904,6 +1917,68 @@ base::Optional<MapRef> MapRef::AsElementsKind(ElementsKind kind) const {
return base::Optional<MapRef>();
}
void MapRef::SerializeForElementLoad() {
CHECK_EQ(broker()->mode(), JSHeapBroker::kSerializing);
data()->AsMap()->SerializeForElementLoad(broker());
}
void MapRef::SerializeForElementStore() {
CHECK_EQ(broker()->mode(), JSHeapBroker::kSerializing);
data()->AsMap()->SerializeForElementStore(broker());
}
namespace {
// This helper function has two modes. If {prototype_maps} is nullptr, the
// prototype chain is serialized as necessary to determine the result.
// Otherwise, the heap is untouched and the encountered prototypes are pushed
// onto {prototype_maps}.
bool HasOnlyStablePrototypesWithFastElementsHelper(
JSHeapBroker* broker, MapRef const& map,
ZoneVector<MapRef>* prototype_maps) {
for (MapRef prototype_map = map;;) {
if (prototype_maps == nullptr) prototype_map.SerializePrototype();
prototype_map = prototype_map.prototype().AsHeapObject().map();
if (prototype_map.oddball_type() == OddballType::kNull) return true;
if (!map.prototype().IsJSObject() || !prototype_map.is_stable() ||
!IsFastElementsKind(prototype_map.elements_kind())) {
return false;
}
if (prototype_maps != nullptr) prototype_maps->push_back(prototype_map);
}
}
} // namespace
void MapData::SerializeForElementLoad(JSHeapBroker* broker) {
if (serialized_for_element_load_) return;
serialized_for_element_load_ = true;
TraceScope tracer(broker, this, "MapData::SerializeForElementLoad");
SerializePrototype(broker);
}
void MapData::SerializeForElementStore(JSHeapBroker* broker) {
if (serialized_for_element_store_) return;
serialized_for_element_store_ = true;
TraceScope tracer(broker, this, "MapData::SerializeForElementStore");
HasOnlyStablePrototypesWithFastElementsHelper(broker, MapRef(broker, this),
nullptr);
}
bool MapRef::HasOnlyStablePrototypesWithFastElements(
ZoneVector<MapRef>* prototype_maps) {
for (MapRef prototype_map = *this;;) {
if (prototype_maps == nullptr) prototype_map.SerializePrototype();
prototype_map = prototype_map.prototype().AsHeapObject().map();
if (prototype_map.oddball_type() == OddballType::kNull) return true;
if (!prototype().IsJSObject() || !prototype_map.is_stable() ||
!IsFastElementsKind(prototype_map.elements_kind())) {
return false;
}
if (prototype_maps != nullptr) prototype_maps->push_back(prototype_map);
}
}
bool MapRef::supports_fast_array_iteration() const {
if (broker()->mode() == JSHeapBroker::kDisabled) {
AllowHandleDereference allow_handle_dereference;
......@@ -2272,10 +2347,14 @@ BIMODAL_ACCESSOR_B(Map, bit_field3, NumberOfOwnDescriptors,
Map::NumberOfOwnDescriptorsBits)
BIMODAL_ACCESSOR_B(Map, bit_field3, has_hidden_prototype,
Map::HasHiddenPrototypeBit)
BIMODAL_ACCESSOR_B(Map, bit_field3, is_migration_target,
Map::IsMigrationTargetBit)
BIMODAL_ACCESSOR_B(Map, bit_field, has_prototype_slot, Map::HasPrototypeSlotBit)
BIMODAL_ACCESSOR_B(Map, bit_field, is_access_check_needed,
Map::IsAccessCheckNeededBit)
BIMODAL_ACCESSOR_B(Map, bit_field, is_callable, Map::IsCallableBit)
BIMODAL_ACCESSOR_B(Map, bit_field, has_indexed_interceptor,
Map::HasIndexedInterceptorBit)
BIMODAL_ACCESSOR_B(Map, bit_field, is_constructor, Map::IsConstructorBit)
BIMODAL_ACCESSOR_B(Map, bit_field, is_undetectable, Map::IsUndetectableBit)
BIMODAL_ACCESSOR_C(Map, int, instance_size)
......@@ -2751,17 +2830,22 @@ void JSTypedArrayRef::Serialize() {
data()->AsJSTypedArray()->Serialize(broker());
}
bool JSTypedArrayRef::serialized() const {
CHECK_NE(broker()->mode(), JSHeapBroker::kDisabled);
return data()->AsJSTypedArray()->serialized();
}
void JSBoundFunctionRef::Serialize() {
if (broker()->mode() == JSHeapBroker::kDisabled) return;
CHECK_EQ(broker()->mode(), JSHeapBroker::kSerializing);
data()->AsJSBoundFunction()->Serialize(broker());
}
bool CanInlineElementAccess(Handle<Map> map) {
if (!map->IsJSObjectMap()) return false;
if (map->is_access_check_needed()) return false;
if (map->has_indexed_interceptor()) return false;
ElementsKind const elements_kind = map->elements_kind();
bool CanInlineElementAccess(MapRef const& map) {
if (!map.IsJSObjectMap()) return false;
if (map.is_access_check_needed()) return false;
if (map.has_indexed_interceptor()) return false;
ElementsKind const elements_kind = map.elements_kind();
if (IsFastElementsKind(elements_kind)) return true;
if (IsFixedTypedArrayElementsKind(elements_kind) &&
elements_kind != BIGUINT64_ELEMENTS &&
......@@ -2771,30 +2855,68 @@ bool CanInlineElementAccess(Handle<Map> map) {
return false;
}
bool JSHeapBroker::HasFeedback(FeedbackNexus const& nexus) const {
return feedback_.find(nexus) != feedback_.end();
ProcessedFeedback::ProcessedFeedback(Zone* zone)
: receiver_maps(zone), transitions(zone) {}
ProcessedFeedback::MapIterator::MapIterator(ProcessedFeedback const& processed,
JSHeapBroker* broker)
: processed_(processed), broker_(broker) {
CHECK_LT(processed.receiver_maps.size(),
std::numeric_limits<size_t>::max() - processed.transitions.size());
}
bool ProcessedFeedback::MapIterator::done() const {
return index_ >=
processed_.receiver_maps.size() + processed_.transitions.size();
}
ProcessedFeedback& JSHeapBroker::GetOrCreateFeedback(
void ProcessedFeedback::MapIterator::advance() { index_++; }
MapRef ProcessedFeedback::MapIterator::current() const {
CHECK(!done());
size_t receiver_maps_size = processed_.receiver_maps.size();
Handle<Map> map;
if (index_ < receiver_maps_size) {
map = processed_.receiver_maps[index_];
} else {
map = processed_.transitions[index_ - receiver_maps_size].first;
}
return MapRef(broker_, map);
}
ProcessedFeedback::MapIterator ProcessedFeedback::all_maps(
JSHeapBroker* broker) const {
return MapIterator(*this, broker);
}
ProcessedFeedback& JSHeapBroker::CreateEmptyFeedback(
FeedbackNexus const& nexus) {
auto it = feedback_.find(nexus);
if (it != feedback_.end()) return it->second;
auto insertion = feedback_.insert({nexus, ProcessedFeedback(zone())});
CHECK(insertion.second);
return insertion.first->second;
}
void ProcessFeedbackMapsForElementAccess(Isolate* isolate,
bool JSHeapBroker::HasFeedback(FeedbackNexus const& nexus) const {
return feedback_.find(nexus) != feedback_.end();
}
ProcessedFeedback& JSHeapBroker::GetFeedback(FeedbackNexus const& nexus) {
auto it = feedback_.find(nexus);
CHECK_NE(it, feedback_.end());
return it->second;
}
void ProcessFeedbackMapsForElementAccess(JSHeapBroker* broker,
MapHandles const& maps,
ProcessedFeedback* processed) {
DCHECK(processed->receiver_maps.empty());
DCHECK(processed->transitions.empty());
CHECK(processed->receiver_maps.empty());
CHECK(processed->transitions.empty());
// Collect possible transition targets.
MapHandles possible_transition_targets;
possible_transition_targets.reserve(maps.size());
for (Handle<Map> map : maps) {
if (CanInlineElementAccess(map) &&
if (CanInlineElementAccess(MapRef(broker, map)) &&
IsFastElementsKind(map->elements_kind()) &&
GetInitialFastElementsKind() != map->elements_kind()) {
possible_transition_targets.push_back(map);
......@@ -2804,17 +2926,31 @@ void ProcessFeedbackMapsForElementAccess(Isolate* isolate,
// Separate the actual receiver maps and the possible transition sources.
for (Handle<Map> map : maps) {
// Don't generate elements kind transitions from stable maps.
Map transition_target = map->is_stable()
? Map()
: map->FindElementsKindTransitionedMap(
isolate, possible_transition_targets);
Map transition_target =
map->is_stable() ? Map()
: map->FindElementsKindTransitionedMap(
broker->isolate(), possible_transition_targets);
if (transition_target.is_null()) {
processed->receiver_maps.push_back(map);
} else {
processed->transitions.emplace_back(map,
handle(transition_target, isolate));
processed->transitions.emplace_back(
map, handle(transition_target, broker->isolate()));
}
}
#ifdef ENABLE_SLOW_DCHECKS
// No transition sources appear in {receiver_maps}.
// All transition targets appear in {receiver_maps}.
for (auto& transition : processed->transitions) {
USE(transition);
CHECK(std::none_of(
processed->receiver_maps.cbegin(), processed->receiver_maps.cend(),
[&](Handle<Map> map) { return map.equals(transition.first); }));
CHECK(std::any_of(
processed->receiver_maps.cbegin(), processed->receiver_maps.cend(),
[&](Handle<Map> map) { return map.equals(transition.second); }));
}
#endif
}
#undef BIMODAL_ACCESSOR
......
......@@ -439,7 +439,9 @@ class MapRef : public HeapObjectRef {
bool IsPrimitiveMap() const;
bool is_undetectable() const;
bool is_callable() const;
bool has_indexed_interceptor() const;
bool has_hidden_prototype() const;
bool is_migration_target() const;
bool supports_fast_array_iteration() const;
bool supports_fast_array_resize() const;
......@@ -448,13 +450,17 @@ class MapRef : public HeapObjectRef {
#undef DEF_TESTER
ObjectRef GetConstructor() const;
OddballType oddball_type() const;
base::Optional<MapRef> AsElementsKind(ElementsKind kind) const;
void SerializePrototype();
ObjectRef prototype() const;
OddballType oddball_type() const;
void SerializeForElementLoad();
base::Optional<MapRef> AsElementsKind(ElementsKind kind) const;
void SerializeForElementStore();
bool HasOnlyStablePrototypesWithFastElements(
ZoneVector<MapRef>* prototype_maps);
// Concerning the underlying instance_descriptors:
void SerializeOwnDescriptors();
......@@ -573,6 +579,7 @@ class JSTypedArrayRef : public JSObjectRef {
void* elements_external_pointer() const;
void Serialize();
bool serialized() const;
HeapObjectRef buffer() const;
};
......@@ -617,11 +624,32 @@ class InternalizedStringRef : public StringRef {
};
struct ProcessedFeedback {
explicit ProcessedFeedback(Zone* zone);
// No transition sources appear in {receiver_maps}.
// All transition targets appear in {receiver_maps}.
ZoneVector<Handle<Map>> receiver_maps;
ZoneVector<std::pair<Handle<Map>, Handle<Map>>> transitions;
explicit ProcessedFeedback(Zone* zone)
: receiver_maps(zone), transitions(zone) {}
class MapIterator {
public:
bool done() const;
void advance();
MapRef current() const;
private:
friend struct ProcessedFeedback;
explicit MapIterator(ProcessedFeedback const& processed,
JSHeapBroker* broker);
ProcessedFeedback const& processed_;
JSHeapBroker* const broker_;
size_t index_ = 0;
};
// Iterator over all maps: first {receiver_maps}, then transition sources.
MapIterator all_maps(JSHeapBroker* broker) const;
};
class V8_EXPORT_PRIVATE JSHeapBroker : public NON_EXPORTED_BASE(ZoneObject) {
......@@ -654,8 +682,9 @@ class V8_EXPORT_PRIVATE JSHeapBroker : public NON_EXPORTED_BASE(ZoneObject) {
// %ObjectPrototype%.
bool IsArrayOrObjectPrototype(const JSObjectRef& object) const;
ProcessedFeedback& CreateEmptyFeedback(FeedbackNexus const& nexus);
bool HasFeedback(FeedbackNexus const& nexus) const;
ProcessedFeedback& GetOrCreateFeedback(FeedbackNexus const& nexus);
ProcessedFeedback& GetFeedback(FeedbackNexus const& nexus);
std::ostream& Trace();
void IncrementTracingIndentation();
......@@ -671,7 +700,7 @@ class V8_EXPORT_PRIVATE JSHeapBroker : public NON_EXPORTED_BASE(ZoneObject) {
struct FeedbackNexusHash {
size_t operator()(FeedbackNexus const& nexus) const {
return base::hash_combine(nexus.vector_handle().location(), nexus.slot());
return base::hash_combine(nexus.vector_handle().address(), nexus.slot());
}
};
struct FeedbackNexusEqual {
......@@ -714,8 +743,8 @@ Reduction NoChangeBecauseOfMissingData(JSHeapBroker* broker,
// Miscellaneous definitions that should be moved elsewhere once concurrent
// compilation is finished.
bool CanInlineElementAccess(Handle<Map> map);
void ProcessFeedbackMapsForElementAccess(Isolate* isolate,
bool CanInlineElementAccess(MapRef const& map);
void ProcessFeedbackMapsForElementAccess(JSHeapBroker* broker,
MapHandles const& maps,
ProcessedFeedback* processed);
......
......@@ -453,10 +453,6 @@ Reduction JSInliner::ReduceJSCall(Node* node) {
SharedFunctionInfo::EnsureSourcePositionsAvailable(isolate(), shared_info);
}
// ----------------------------------------------------------------
// After this point, we've made a decision to inline this function.
// We shall not bailout from inlining if we got here.
TRACE("Inlining %s into %s%s\n", shared_info->DebugName()->ToCString().get(),
info_->shared_info()->DebugName()->ToCString().get(),
(exception_target != nullptr) ? " (inside try-block)" : "");
......@@ -470,13 +466,17 @@ Reduction JSInliner::ReduceJSCall(Node* node) {
SharedFunctionInfoRef sfi(broker(), shared_info);
FeedbackVectorRef feedback(broker(), feedback_vector);
if (!sfi.IsSerializedForCompilation(feedback)) {
TRACE_BROKER(broker(),
"Would have missed opportunity to inline a function ("
<< Brief(*sfi.object()) << " with "
<< Brief(*feedback.object()) << ")");
TRACE_BROKER(broker(), "Missed opportunity to inline a function ("
<< Brief(*sfi.object()) << " with "
<< Brief(*feedback.object()) << ")");
return NoChange();
}
}
// ----------------------------------------------------------------
// After this point, we've made a decision to inline this function.
// We shall not bailout from inlining if we got here.
Handle<BytecodeArray> bytecode_array =
handle(shared_info->GetBytecodeArray(), isolate());
......
......@@ -1168,9 +1168,11 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
PropertyAccessInfo access_info = access_infos.front();
// Try to build string check or number check if possible.
// Otherwise build a map check.
if (!access_builder.TryBuildStringCheck(access_info.receiver_maps(),
if (!access_builder.TryBuildStringCheck(broker(),
access_info.receiver_maps(),
&receiver, &effect, control) &&
!access_builder.TryBuildNumberCheck(access_info.receiver_maps(),
!access_builder.TryBuildNumberCheck(broker(),
access_info.receiver_maps(),
&receiver, &effect, control)) {
if (HasNumberMaps(broker(), access_info.receiver_maps())) {
// We need to also let Smi {receiver}s through in this case, so
......@@ -1310,7 +1312,7 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
// {receiver} here to make sure that TurboFan knows that along this
// path the {this_receiver} is a String. This is because we want
// strict checking of types, for example for StringLength operators.
if (HasOnlyStringMaps(receiver_maps)) {
if (HasOnlyStringMaps(broker(), receiver_maps)) {
this_receiver = this_effect =
graph()->NewNode(common()->TypeGuard(Type::String()), receiver,
this_effect, this_control);
......@@ -1499,10 +1501,25 @@ Reduction JSNativeContextSpecialization::ReduceElementAccessOnString(
return Replace(value);
}
namespace {
base::Optional<JSTypedArrayRef> GetTypedArrayConstant(JSHeapBroker* broker,
Node* receiver) {
HeapObjectMatcher m(receiver);
if (!m.HasValue()) return base::nullopt;
ObjectRef object = m.Ref(broker);
if (!object.IsJSTypedArray()) return base::nullopt;
JSTypedArrayRef typed_array = object.AsJSTypedArray();
if (typed_array.is_on_heap()) return base::nullopt;
return typed_array;
}
} // namespace
Reduction JSNativeContextSpecialization::ReduceElementAccess(
Node* node, Node* index, Node* value, FeedbackNexus const& nexus,
MapHandles const& receiver_maps, AccessMode access_mode,
KeyedAccessLoadMode load_mode, KeyedAccessStoreMode store_mode) {
DisallowHeapAccessIf no_heap_access(FLAG_concurrent_inlining);
DCHECK(node->opcode() == IrOpcode::kJSLoadProperty ||
node->opcode() == IrOpcode::kJSStoreProperty ||
node->opcode() == IrOpcode::kJSStoreInArrayLiteral ||
......@@ -1512,7 +1529,7 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
Node* control = NodeProperties::GetControlInput(node);
Node* frame_state = NodeProperties::FindFrameStateBefore(node);
if (HasOnlyStringMaps(receiver_maps)) {
if (HasOnlyStringMaps(broker(), receiver_maps)) {
return ReduceElementAccessOnString(node, index, value, access_mode,
load_mode);
}
......@@ -1539,33 +1556,24 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
// TODO(turbofan): We could have a fast path here, that checks for the
// common case of Array or Object prototype only and therefore avoids
// the zone allocation of this vector.
ZoneVector<Handle<Map>> prototype_maps(zone());
ZoneVector<MapRef> prototype_maps(zone());
for (ElementAccessInfo const& access_info : access_infos) {
for (Handle<Map> receiver_map : access_info.receiver_maps()) {
for (Handle<Map> map : access_info.receiver_maps()) {
MapRef receiver_map(broker(), map);
// If the {receiver_map} has a prototype and its elements backing
// store is either holey, or we have a potentially growing store,
// then we need to check that all prototypes have stable maps with
// fast elements (and we need to guard against changes to that below).
if (IsHoleyOrDictionaryElementsKind(receiver_map->elements_kind()) ||
IsGrowStoreMode(store_mode)) {
// Make sure all prototypes are stable and have fast elements.
for (Handle<Map> map = receiver_map;;) {
Handle<Object> map_prototype(map->prototype(), isolate());
if (map_prototype->IsNull(isolate())) break;
if (!map_prototype->IsJSObject()) return NoChange();
map =
handle(Handle<JSObject>::cast(map_prototype)->map(), isolate());
if (!map->is_stable()) return NoChange();
if (!IsFastElementsKind(map->elements_kind())) return NoChange();
prototype_maps.push_back(map);
}
if ((IsHoleyOrDictionaryElementsKind(receiver_map.elements_kind()) ||
IsGrowStoreMode(store_mode)) &&
!receiver_map.HasOnlyStablePrototypesWithFastElements(
&prototype_maps)) {
return NoChange();
}
}
}
// Install dependencies on the relevant prototype maps.
for (Handle<Map> prototype_map : prototype_maps) {
dependencies()->DependOnStableMap(MapRef(broker(), prototype_map));
for (MapRef const& prototype_map : prototype_maps) {
dependencies()->DependOnStableMap(prototype_map);
}
} else if (access_mode == AccessMode::kHas) {
// If we have any fast arrays, we need to check and depend on
......@@ -1584,21 +1592,39 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
PropertyAccessBuilder access_builder(jsgraph(), broker(), dependencies());
receiver = access_builder.BuildCheckHeapObject(receiver, &effect, control);
// Check if we have the necessary data for building element accesses.
for (ElementAccessInfo const& access_info : access_infos) {
if (!IsFixedTypedArrayElementsKind(access_info.elements_kind())) continue;
base::Optional<JSTypedArrayRef> typed_array =
GetTypedArrayConstant(broker(), receiver);
if (typed_array.has_value()) {
if (!FLAG_concurrent_inlining) {
typed_array->Serialize();
} else if (!typed_array->serialized()) {
TRACE_BROKER(broker(),
"ReduceElementAccess: missing data for typed array "
<< typed_array->object().address() << "\n");
return NoChange();
}
}
}
// Check for the monomorphic case.
if (access_infos.size() == 1) {
ElementAccessInfo access_info = access_infos.front();
// Perform possible elements kind transitions.
Handle<Map> const transition_target = access_info.receiver_maps().front();
for (auto transition_source : access_info.transition_sources()) {
MapRef transition_target(broker(), access_info.receiver_maps().front());
for (auto source : access_info.transition_sources()) {
DCHECK_EQ(access_info.receiver_maps().size(), 1);
MapRef transition_source(broker(), source);
effect = graph()->NewNode(
simplified()->TransitionElementsKind(ElementsTransition(
IsSimpleMapChangeTransition(transition_source->elements_kind(),
transition_target->elements_kind())
IsSimpleMapChangeTransition(transition_source.elements_kind(),
transition_target.elements_kind())
? ElementsTransition::kFastTransition
: ElementsTransition::kSlowTransition,
transition_source, transition_target)),
transition_source.object(), transition_target.object())),
receiver, effect, control);
}
......@@ -1639,17 +1665,18 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
Node* this_control = fallthrough_control;
// Perform possible elements kind transitions.
Handle<Map> const transition_target = access_info.receiver_maps().front();
for (auto transition_source : access_info.transition_sources()) {
MapRef transition_target(broker(), access_info.receiver_maps().front());
for (auto source : access_info.transition_sources()) {
MapRef transition_source(broker(), source);
DCHECK_EQ(access_info.receiver_maps().size(), 1);
this_effect = graph()->NewNode(
simplified()->TransitionElementsKind(ElementsTransition(
IsSimpleMapChangeTransition(transition_source->elements_kind(),
transition_target->elements_kind())
IsSimpleMapChangeTransition(transition_source.elements_kind(),
transition_target.elements_kind())
? ElementsTransition::kFastTransition
: ElementsTransition::kSlowTransition,
transition_source, transition_target)),
receiver, this_effect, this_control);
transition_source.object(), transition_target.object())),
receiver, effect, control);
}
// Perform map check(s) on {receiver}.
......@@ -2604,17 +2631,6 @@ ExternalArrayType GetArrayTypeFromElementsKind(ElementsKind kind) {
UNREACHABLE();
}
base::Optional<JSTypedArrayRef> GetTypedArrayConstant(JSHeapBroker* broker,
Node* receiver) {
HeapObjectMatcher m(receiver);
if (!m.HasValue()) return base::nullopt;
ObjectRef object = m.Ref(broker);
if (!object.IsJSTypedArray()) return base::nullopt;
JSTypedArrayRef typed_array = object.AsJSTypedArray();
if (typed_array.is_on_heap()) return base::nullopt;
return typed_array;
}
} // namespace
JSNativeContextSpecialization::ValueEffectControl
......@@ -2633,12 +2649,11 @@ JSNativeContextSpecialization::BuildElementAccess(
Node* base_pointer;
Node* external_pointer;
// Check if we can constant-fold information about the {receiver} (i.e.
// Check if we can constant-fold information about the {receiver} (e.g.
// for asm.js-like code patterns).
base::Optional<JSTypedArrayRef> typed_array =
GetTypedArrayConstant(broker(), receiver);
if (typed_array.has_value()) {
typed_array->Serialize();
buffer = jsgraph()->Constant(typed_array->buffer());
length =
jsgraph()->Constant(static_cast<double>(typed_array->length_value()));
......@@ -3312,9 +3327,7 @@ bool JSNativeContextSpecialization::CanTreatHoleAsUndefined(
// native contexts, as the global Array protector works isolate-wide).
for (Handle<Map> map : receiver_maps) {
MapRef receiver_map(broker(), map);
// TODO(neis): Remove SerializePrototype call once brokerization is
// complete.
receiver_map.SerializePrototype();
if (!FLAG_concurrent_inlining) receiver_map.SerializePrototype();
ObjectRef receiver_prototype = receiver_map.prototype();
if (!receiver_prototype.IsJSObject() ||
!broker()->IsArrayOrObjectPrototype(receiver_prototype.AsJSObject())) {
......
......@@ -32,28 +32,31 @@ SimplifiedOperatorBuilder* PropertyAccessBuilder::simplified() const {
return jsgraph()->simplified();
}
bool HasOnlyStringMaps(MapHandles const& maps) {
bool HasOnlyStringMaps(JSHeapBroker* broker, MapHandles const& maps) {
for (auto map : maps) {
if (!map->IsStringMap()) return false;
MapRef map_ref(broker, map);
if (!map_ref.IsStringMap()) return false;
}
return true;
}
namespace {
bool HasOnlyNumberMaps(MapHandles const& maps) {
bool HasOnlyNumberMaps(JSHeapBroker* broker, MapHandles const& maps) {
for (auto map : maps) {
if (map->instance_type() != HEAP_NUMBER_TYPE) return false;
MapRef map_ref(broker, map);
if (map_ref.instance_type() != HEAP_NUMBER_TYPE) return false;
}
return true;
}
} // namespace
bool PropertyAccessBuilder::TryBuildStringCheck(MapHandles const& maps,
bool PropertyAccessBuilder::TryBuildStringCheck(JSHeapBroker* broker,
MapHandles const& maps,
Node** receiver, Node** effect,
Node* control) {
if (HasOnlyStringMaps(maps)) {
if (HasOnlyStringMaps(broker, maps)) {
// Monormorphic string access (ignoring the fact that there are multiple
// String maps).
*receiver = *effect =
......@@ -64,10 +67,11 @@ bool PropertyAccessBuilder::TryBuildStringCheck(MapHandles const& maps,
return false;
}
bool PropertyAccessBuilder::TryBuildNumberCheck(MapHandles const& maps,
bool PropertyAccessBuilder::TryBuildNumberCheck(JSHeapBroker* broker,
MapHandles const& maps,
Node** receiver, Node** effect,
Node* control) {
if (HasOnlyNumberMaps(maps)) {
if (HasOnlyNumberMaps(broker, maps)) {
// Monomorphic number access (we also deal with Smis here).
*receiver = *effect =
graph()->NewNode(simplified()->CheckNumber(VectorSlotPair()), *receiver,
......@@ -139,16 +143,16 @@ Node* PropertyAccessBuilder::BuildCheckHeapObject(Node* receiver, Node** effect,
return receiver;
}
void PropertyAccessBuilder::BuildCheckMaps(
Node* receiver, Node** effect, Node* control,
std::vector<Handle<Map>> const& receiver_maps) {
void PropertyAccessBuilder::BuildCheckMaps(Node* receiver, Node** effect,
Node* control,
MapHandles const& receiver_maps) {
HeapObjectMatcher m(receiver);
if (m.HasValue()) {
Handle<Map> receiver_map(m.Value()->map(), isolate());
if (receiver_map->is_stable()) {
MapRef receiver_map = m.Ref(broker()).AsHeapObject().map();
if (receiver_map.is_stable()) {
for (Handle<Map> map : receiver_maps) {
if (map.is_identical_to(receiver_map)) {
dependencies()->DependOnStableMap(MapRef(broker(), receiver_map));
if (MapRef(broker(), map).equals(receiver_map)) {
dependencies()->DependOnStableMap(receiver_map);
return;
}
}
......@@ -157,8 +161,9 @@ void PropertyAccessBuilder::BuildCheckMaps(
ZoneHandleSet<Map> maps;
CheckMapsFlags flags = CheckMapsFlag::kNone;
for (Handle<Map> map : receiver_maps) {
maps.insert(map, graph()->zone());
if (map->is_migration_target()) {
MapRef receiver_map(broker(), map);
maps.insert(receiver_map.object(), graph()->zone());
if (receiver_map.is_migration_target()) {
flags |= CheckMapsFlag::kTryMigrateInstance;
}
}
......
......@@ -32,11 +32,11 @@ class PropertyAccessBuilder {
// Builds the appropriate string check if the maps are only string
// maps.
bool TryBuildStringCheck(MapHandles const& maps, Node** receiver,
Node** effect, Node* control);
bool TryBuildStringCheck(JSHeapBroker* broker, MapHandles const& maps,
Node** receiver, Node** effect, Node* control);
// Builds a number check if all maps are number maps.
bool TryBuildNumberCheck(MapHandles const& maps, Node** receiver,
Node** effect, Node* control);
bool TryBuildNumberCheck(JSHeapBroker* broker, MapHandles const& maps,
Node** receiver, Node** effect, Node* control);
Node* BuildCheckHeapObject(Node* receiver, Node** effect, Node* control);
void BuildCheckMaps(Node* receiver, Node** effect, Node* control,
......@@ -71,7 +71,7 @@ class PropertyAccessBuilder {
CompilationDependencies* dependencies_;
};
bool HasOnlyStringMaps(MapHandles const& maps);
bool HasOnlyStringMaps(JSHeapBroker* broker, MapHandles const& maps);
} // namespace compiler
} // namespace internal
......
......@@ -684,56 +684,79 @@ void SerializerForBackgroundCompilation::VisitConstructWithSpread(
}
void SerializerForBackgroundCompilation::ProcessFeedbackForKeyedPropertyAccess(
BytecodeArrayIterator* iterator) {
interpreter::Bytecode bytecode = iterator->current_bytecode();
DCHECK(bytecode == interpreter::Bytecode::kLdaKeyedProperty ||
bytecode == interpreter::Bytecode::kStaKeyedProperty ||
bytecode == interpreter::Bytecode::kStaInArrayLiteral);
if (environment()->function().feedback_vector.is_null()) return;
FeedbackSlot slot = iterator->GetSlotOperand(
bytecode == interpreter::Bytecode::kLdaKeyedProperty ? 1 : 2);
FeedbackSlot slot, AccessMode mode) {
if (slot.IsInvalid()) return;
if (environment()->function().feedback_vector.is_null()) return;
FeedbackNexus nexus(environment()->function().feedback_vector, slot);
if (broker()->HasFeedback(nexus)) return;
Handle<Name> name(nexus.GetName(), broker()->isolate());
CHECK_IMPLIES(nexus.GetKeyType() == ELEMENT, name->is_null());
if (!name->is_null() || nexus.GetKeyType() == PROPERTY) {
CHECK_NE(bytecode, interpreter::Bytecode::kStaInArrayLiteral);
if (nexus.ic_state() == MEGAMORPHIC) return;
if (nexus.GetKeyType() == PROPERTY) {
CHECK_NE(mode, AccessMode::kStoreInLiteral);
return; // TODO(neis): Support named access.
}
if (nexus.ic_state() == MEGAMORPHIC) {
return;
}
DCHECK_EQ(nexus.GetKeyType(), ELEMENT);
CHECK(nexus.GetName().is_null());
ProcessedFeedback& processed = broker()->GetOrCreateFeedback(nexus);
MapHandles maps;
nexus.ExtractMaps(&maps);
ProcessFeedbackMapsForElementAccess(broker()->isolate(), maps, &processed);
// TODO(neis): Have something like MapRef::SerializeForElementStore() and call
// it for every receiver map in case of an element store.
ProcessedFeedback& processed = broker()->CreateEmptyFeedback(nexus);
ProcessFeedbackMapsForElementAccess(broker(), maps, &processed);
for (ProcessedFeedback::MapIterator it = processed.all_maps(broker());
!it.done(); it.advance()) {
switch (mode) {
case AccessMode::kHas:
case AccessMode::kLoad:
it.current().SerializeForElementLoad();
break;
case AccessMode::kStore:
it.current().SerializeForElementStore();
break;
case AccessMode::kStoreInLiteral:
// This operation is fairly local and simple, nothing to serialize.
break;
}
}
}
void SerializerForBackgroundCompilation::VisitLdaKeyedProperty(
BytecodeArrayIterator* iterator) {
FeedbackSlot slot = iterator->GetSlotOperand(1);
ProcessFeedbackForKeyedPropertyAccess(slot, AccessMode::kLoad);
environment()->accumulator_hints().Clear();
}
void SerializerForBackgroundCompilation::VisitTestIn(
BytecodeArrayIterator* iterator) {
FeedbackSlot slot = iterator->GetSlotOperand(1);
ProcessFeedbackForKeyedPropertyAccess(slot, AccessMode::kHas);
environment()->accumulator_hints().Clear();
ProcessFeedbackForKeyedPropertyAccess(iterator);
}
void SerializerForBackgroundCompilation::VisitStaKeyedProperty(
BytecodeArrayIterator* iterator) {
const Hints& receiver =
environment()->register_hints(iterator->GetRegisterOperand(0));
FeedbackSlot slot = iterator->GetSlotOperand(2);
ProcessFeedbackForKeyedPropertyAccess(slot, AccessMode::kStore);
// Process hints about constants.
for (Handle<Object> object : receiver.constants()) {
if (object->IsJSTypedArray()) JSTypedArrayRef(broker(), object).Serialize();
}
environment()->accumulator_hints().Clear();
ProcessFeedbackForKeyedPropertyAccess(iterator);
}
void SerializerForBackgroundCompilation::VisitStaInArrayLiteral(
BytecodeArrayIterator* iterator) {
FeedbackSlot slot = iterator->GetSlotOperand(2);
ProcessFeedbackForKeyedPropertyAccess(slot, AccessMode::kStoreInLiteral);
environment()->accumulator_hints().Clear();
ProcessFeedbackForKeyedPropertyAccess(iterator);
}
#define DEFINE_CLEAR_ENVIRONMENT(name, ...) \
......
......@@ -6,6 +6,7 @@
#define V8_COMPILER_SERIALIZER_FOR_BACKGROUND_COMPILATION_H_
#include "src/base/optional.h"
#include "src/compiler/access-info.h"
#include "src/handles.h"
#include "src/maybe-handles.h"
#include "src/utils.h"
......@@ -95,7 +96,6 @@ namespace compiler {
V(TestGreaterThanOrEqual) \
V(TestReferenceEqual) \
V(TestInstanceOf) \
V(TestIn) \
V(TestUndetectable) \
V(TestNull) \
V(TestUndefined) \
......@@ -134,6 +134,7 @@ namespace compiler {
V(StaInArrayLiteral) \
V(StaKeyedProperty) \
V(Star) \
V(TestIn) \
V(Wide) \
CLEAR_ENVIRONMENT_LIST(V) \
CLEAR_ACCUMULATOR_LIST(V) \
......@@ -244,8 +245,8 @@ class SerializerForBackgroundCompilation {
base::Optional<Hints> new_target,
const HintsVector& arguments, bool with_spread);
void ProcessFeedbackForKeyedPropertyAccess(
interpreter::BytecodeArrayIterator* iterator);
void ProcessFeedbackForKeyedPropertyAccess(FeedbackSlot slot,
AccessMode mode);
JSHeapBroker* broker() const { return broker_; }
Zone* zone() const { return zone_; }
......
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