[turbofan] Allow for multiple re-serializations of a function

Implement the possibility to revisit the same function in the
serializer using equality of its arguments.

Bug: v8:7790
Change-Id: I609a6009bf503e378e50d0b32c6f1c13721d2557
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1863198
Commit-Queue: Maya Lekova <mslekova@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64683}
This commit is contained in:
Maya Lekova 2019-10-31 14:43:00 +01:00 committed by Commit Bot
parent 35b5ada087
commit 45a2058b48
10 changed files with 245 additions and 164 deletions

View File

@ -1820,6 +1820,7 @@ v8_compiler_sources = [
"src/compiler/select-lowering.h",
"src/compiler/serializer-for-background-compilation.cc",
"src/compiler/serializer-for-background-compilation.h",
"src/compiler/serializer-hints.h",
"src/compiler/simd-scalar-lowering.cc",
"src/compiler/simd-scalar-lowering.h",
"src/compiler/simplified-lowering.cc",

View File

@ -4168,7 +4168,7 @@ void BuildGraphFromBytecode(JSHeapBroker* broker, Zone* local_zone,
SourcePositionTable* source_positions,
int inlining_id, BytecodeGraphBuilderFlags flags,
TickCounter* tick_counter) {
DCHECK(shared_info.IsSerializedForCompilation(feedback_vector));
DCHECK(broker->IsSerializedForCompilation(shared_info, feedback_vector));
BytecodeGraphBuilder builder(
broker, local_zone, broker->target_native_context(), shared_info,
feedback_vector, osr_offset, jsgraph, invocation_frequency,

View File

@ -803,9 +803,6 @@ class V8_EXPORT_PRIVATE SharedFunctionInfoRef : public HeapObjectRef {
return GetInlineability() == SharedFunctionInfo::kIsInlineable;
}
bool IsSerializedForCompilation(FeedbackVectorRef feedback) const;
void SetSerializedForCompilation(FeedbackVectorRef feedback);
// Template objects may not be created at compilation time. This method
// wraps the retrieval of the template object and creates it if
// necessary.

View File

@ -1645,9 +1645,6 @@ class SharedFunctionInfoData : public HeapObjectData {
int builtin_id() const { return builtin_id_; }
int context_header_size() const { return context_header_size_; }
BytecodeArrayData* GetBytecodeArray() const { return GetBytecodeArray_; }
void SetSerializedForCompilation(JSHeapBroker* broker,
FeedbackVectorRef feedback);
bool IsSerializedForCompilation(FeedbackVectorRef feedback) const;
void SerializeFunctionTemplateInfo(JSHeapBroker* broker);
ScopeInfoData* scope_info() const { return scope_info_; }
void SerializeScopeInfoChain(JSHeapBroker* broker);
@ -1675,9 +1672,6 @@ class SharedFunctionInfoData : public HeapObjectData {
int const builtin_id_;
int context_header_size_;
BytecodeArrayData* const GetBytecodeArray_;
ZoneUnorderedSet<Handle<FeedbackVector>, Handle<FeedbackVector>::hash,
Handle<FeedbackVector>::equal_to>
serialized_for_compilation_;
#define DECL_MEMBER(type, name) type const name##_;
BROKER_SFI_FIELDS(DECL_MEMBER)
#undef DECL_MEMBER
@ -1697,8 +1691,7 @@ SharedFunctionInfoData::SharedFunctionInfoData(
object->HasBytecodeArray()
? broker->GetOrCreateData(object->GetBytecodeArray())
->AsBytecodeArray()
: nullptr),
serialized_for_compilation_(broker->zone())
: nullptr)
#define INIT_MEMBER(type, name) , name##_(object->name())
BROKER_SFI_FIELDS(INIT_MEMBER)
#undef INIT_MEMBER
@ -1710,13 +1703,6 @@ SharedFunctionInfoData::SharedFunctionInfoData(
DCHECK_EQ(HasBytecodeArray_, GetBytecodeArray_ != nullptr);
}
void SharedFunctionInfoData::SetSerializedForCompilation(
JSHeapBroker* broker, FeedbackVectorRef feedback) {
CHECK(serialized_for_compilation_.insert(feedback.object()).second);
TRACE(broker, "Set function " << this << " with " << feedback
<< " as serialized for compilation");
}
void SharedFunctionInfoData::SerializeFunctionTemplateInfo(
JSHeapBroker* broker) {
if (function_template_info_) return;
@ -1739,12 +1725,6 @@ void SharedFunctionInfoData::SerializeScopeInfoChain(JSHeapBroker* broker) {
scope_info_->SerializeScopeInfoChain(broker);
}
bool SharedFunctionInfoData::IsSerializedForCompilation(
FeedbackVectorRef feedback) const {
return serialized_for_compilation_.find(feedback.object()) !=
serialized_for_compilation_.end();
}
class SourceTextModuleData : public HeapObjectData {
public:
SourceTextModuleData(JSHeapBroker* broker, ObjectData** storage,
@ -2280,7 +2260,8 @@ JSHeapBroker::JSHeapBroker(Isolate* isolate, Zone* broker_zone,
feedback_(zone()),
bytecode_analyses_(zone()),
property_access_infos_(zone()),
typed_array_string_tags_(zone()) {
typed_array_string_tags_(zone()),
serialized_functions_(zone()) {
// Note that this initialization of {refs_} with the minimal initial capacity
// is redundant in the normal use case (concurrent compilation enabled,
// standard objects to be serialized), as the map is going to be replaced
@ -2531,6 +2512,42 @@ StringRef JSHeapBroker::GetTypedArrayStringTag(ElementsKind kind) {
return StringRef(this, typed_array_string_tags_[idx]);
}
bool JSHeapBroker::ShouldBeSerializedForCompilation(
const SharedFunctionInfoRef& shared, const FeedbackVectorRef& feedback,
const HintsVector& arguments) const {
SerializedFunction function{shared, feedback};
auto matching_functions = serialized_functions_.equal_range(function);
return std::find_if(matching_functions.first, matching_functions.second,
[&arguments](const auto& entry) {
return entry.second == arguments;
}) == matching_functions.second;
}
void JSHeapBroker::SetSerializedForCompilation(
const SharedFunctionInfoRef& shared, const FeedbackVectorRef& feedback,
const HintsVector& arguments) {
HintsVector arguments_copy_in_broker_zone(zone());
for (auto const& hints : arguments) {
Hints hint_copy_in_broker_zone;
hint_copy_in_broker_zone.AddFromChildSerializer(hints, zone());
arguments_copy_in_broker_zone.push_back(hint_copy_in_broker_zone);
}
SerializedFunction function = {shared, feedback};
serialized_functions_.insert({function, arguments_copy_in_broker_zone});
TRACE(this, "Set function " << shared << " with " << feedback
<< " as serialized for compilation");
}
bool JSHeapBroker::IsSerializedForCompilation(
const SharedFunctionInfoRef& shared,
const FeedbackVectorRef& feedback) const {
if (mode() == kDisabled) return true;
SerializedFunction function = {shared, feedback};
return serialized_functions_.find(function) != serialized_functions_.end();
}
bool JSHeapBroker::IsArrayOrObjectPrototype(const JSObjectRef& object) const {
if (mode() == kDisabled) {
return isolate()->IsInAnyContext(*object.object(),
@ -4051,14 +4068,6 @@ JSArrayRef SharedFunctionInfoRef::GetTemplateObject(
return JSArrayRef(broker(), array);
}
void SharedFunctionInfoRef::SetSerializedForCompilation(
FeedbackVectorRef feedback) {
CHECK_EQ(broker()->mode(), JSHeapBroker::kSerializing);
CHECK(HasBytecodeArray());
data()->AsSharedFunctionInfo()->SetSerializedForCompilation(broker(),
feedback);
}
void SharedFunctionInfoRef::SerializeFunctionTemplateInfo() {
CHECK_EQ(broker()->mode(), JSHeapBroker::kSerializing);
data()->AsSharedFunctionInfo()->SerializeFunctionTemplateInfo(broker());
@ -4084,12 +4093,6 @@ SharedFunctionInfoRef::function_template_info() const {
return FunctionTemplateInfoRef(broker(), function_template_info);
}
bool SharedFunctionInfoRef::IsSerializedForCompilation(
FeedbackVectorRef feedback) const {
if (broker()->mode() == JSHeapBroker::kDisabled) return HasBytecodeArray();
return data()->AsSharedFunctionInfo()->IsSerializedForCompilation(feedback);
}
int SharedFunctionInfoRef::context_header_size() const {
IF_BROKER_DISABLED_ACCESS_HANDLE_C(SharedFunctionInfo,
scope_info().ContextHeaderLength);

View File

@ -12,6 +12,7 @@
#include "src/compiler/feedback-source.h"
#include "src/compiler/processed-feedback.h"
#include "src/compiler/refs-map.h"
#include "src/compiler/serializer-hints.h"
#include "src/handles/handles.h"
#include "src/interpreter/bytecode-array-accessor.h"
#include "src/objects/feedback-vector.h"
@ -179,6 +180,15 @@ class V8_EXPORT_PRIVATE JSHeapBroker {
StringRef GetTypedArrayStringTag(ElementsKind kind);
bool ShouldBeSerializedForCompilation(const SharedFunctionInfoRef& shared,
const FeedbackVectorRef& feedback,
const HintsVector& arguments) const;
void SetSerializedForCompilation(const SharedFunctionInfoRef& shared,
const FeedbackVectorRef& feedback,
const HintsVector& arguments);
bool IsSerializedForCompilation(const SharedFunctionInfoRef& shared,
const FeedbackVectorRef& feedback) const;
std::ostream& Trace() const;
void IncrementTracingIndentation();
void DecrementTracingIndentation();
@ -240,6 +250,22 @@ class V8_EXPORT_PRIVATE JSHeapBroker {
ZoneVector<ObjectData*> typed_array_string_tags_;
struct SerializedFunction {
SharedFunctionInfoRef shared;
FeedbackVectorRef feedback;
bool operator<(const SerializedFunction& other) const {
if (shared.object().address() < other.shared.object().address()) {
return true;
}
if (shared.object().address() == other.shared.object().address()) {
return feedback.object().address() < other.feedback.object().address();
}
return false;
}
};
ZoneMultimap<SerializedFunction, HintsVector> serialized_functions_;
static const size_t kMinimalRefsBucketCount = 8; // must be power of 2
static const size_t kInitialRefsBucketCount = 1024; // must be power of 2
};

View File

@ -37,7 +37,7 @@ bool CanConsiderForInlining(JSHeapBroker* broker,
}
DCHECK(shared.HasBytecodeArray());
if (!shared.IsSerializedForCompilation(feedback_vector)) {
if (!broker->IsSerializedForCompilation(shared, feedback_vector)) {
TRACE_BROKER_MISSING(
broker, "data for " << shared << " (not serialized for compilation)");
TRACE("Cannot consider " << shared << " for inlining with "

View File

@ -431,7 +431,7 @@ Reduction JSInliner::ReduceJSCall(Node* node) {
// Determine the target's feedback vector and its context.
Node* context;
FeedbackVectorRef feedback_vector = DetermineCallContext(node, &context);
CHECK(shared_info->IsSerializedForCompilation(feedback_vector));
CHECK(broker()->IsSerializedForCompilation(*shared_info, feedback_vector));
// ----------------------------------------------------------------
// After this point, we've made a decision to inline this function.

View File

@ -10,8 +10,8 @@
#include "src/compiler/access-info.h"
#include "src/compiler/bytecode-analysis.h"
#include "src/compiler/compilation-dependencies.h"
#include "src/compiler/functional-list.h"
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/serializer-hints.h"
#include "src/compiler/zone-stats.h"
#include "src/handles/handles-inl.h"
#include "src/ic/call-optimization.h"
@ -232,118 +232,6 @@ namespace compiler {
UNCONDITIONAL_JUMPS_LIST(V) \
UNREACHABLE_BYTECODE_LIST(V)
template <typename T, typename EqualTo>
class FunctionalSet {
public:
void Add(T const& elem, Zone* zone) {
for (auto const& l : data_) {
if (equal_to(l, elem)) return;
}
data_.PushFront(elem, zone);
}
bool Includes(FunctionalSet<T, EqualTo> const& other) const {
return std::all_of(other.begin(), other.end(), [&](T const& other_elem) {
return std::any_of(this->begin(), this->end(), [&](T const& this_elem) {
return equal_to(this_elem, other_elem);
});
});
}
bool IsEmpty() const { return data_.begin() == data_.end(); }
void Clear() { data_.Clear(); }
// Warning: quadratic time complexity.
bool operator==(const FunctionalSet<T, EqualTo>& other) const {
return this->data_.Size() == other.data_.Size() && this->Includes(other) &&
other.Includes(*this);
}
bool operator!=(const FunctionalSet<T, EqualTo>& other) const {
return !(*this == other);
}
using iterator = typename FunctionalList<T>::iterator;
iterator begin() const { return data_.begin(); }
iterator end() const { return data_.end(); }
private:
static EqualTo equal_to;
FunctionalList<T> data_;
};
template <typename T, typename EqualTo>
EqualTo FunctionalSet<T, EqualTo>::equal_to;
struct VirtualContext {
unsigned int distance;
Handle<Context> context;
VirtualContext(unsigned int distance_in, Handle<Context> context_in)
: distance(distance_in), context(context_in) {
CHECK_GT(distance, 0);
}
bool operator==(const VirtualContext& other) const {
return context.equals(other.context) && distance == other.distance;
}
};
class FunctionBlueprint;
struct VirtualBoundFunction;
using ConstantsSet = FunctionalSet<Handle<Object>, Handle<Object>::equal_to>;
using VirtualContextsSet =
FunctionalSet<VirtualContext, std::equal_to<VirtualContext>>;
using MapsSet = FunctionalSet<Handle<Map>, Handle<Map>::equal_to>;
using BlueprintsSet =
FunctionalSet<FunctionBlueprint, std::equal_to<FunctionBlueprint>>;
using BoundFunctionsSet =
FunctionalSet<VirtualBoundFunction, std::equal_to<VirtualBoundFunction>>;
class Hints {
public:
Hints() = default;
static Hints SingleConstant(Handle<Object> constant, Zone* zone);
const ConstantsSet& constants() const;
const MapsSet& maps() const;
const BlueprintsSet& function_blueprints() const;
const VirtualContextsSet& virtual_contexts() const;
const BoundFunctionsSet& virtual_bound_functions() const;
void AddConstant(Handle<Object> constant, Zone* zone);
void AddMap(Handle<Map> map, Zone* zone);
void AddFunctionBlueprint(FunctionBlueprint const& function_blueprint,
Zone* zone);
void AddVirtualContext(VirtualContext const& virtual_context, Zone* zone);
void AddVirtualBoundFunction(VirtualBoundFunction const& bound_function,
Zone* zone);
void Add(const Hints& other, Zone* zone);
void AddFromChildSerializer(const Hints& other, Zone* zone);
void Clear();
bool IsEmpty() const;
bool operator==(Hints const& other) const;
bool operator!=(Hints const& other) const { return !(*this == other); }
#ifdef ENABLE_SLOW_DCHECKS
bool Includes(Hints const& other) const;
bool Equals(Hints const& other) const;
#endif
private:
VirtualContextsSet virtual_contexts_;
ConstantsSet constants_;
MapsSet maps_;
BlueprintsSet function_blueprints_;
BoundFunctionsSet virtual_bound_functions_;
};
using HintsVector = ZoneVector<Hints>;
struct VirtualBoundFunction {
Hints bound_target;
@ -470,7 +358,8 @@ class SerializerForBackgroundCompilation {
ZoneStats* zone_stats, JSHeapBroker* broker,
CompilationDependencies* dependencies, Handle<JSFunction> closure,
SerializerForBackgroundCompilationFlags flags, BailoutId osr_offset);
Hints Run(); // NOTE: Returns empty for an already-serialized function.
Hints Run(); // NOTE: Returns empty for an
// already-serialized function.
class Environment;
@ -616,6 +505,7 @@ class SerializerForBackgroundCompilation {
ZoneUnorderedMap<int, Environment*> jump_target_environments_;
SerializerForBackgroundCompilationFlags const flags_;
BailoutId const osr_offset_;
const HintsVector arguments_;
};
void RunSerializerForBackgroundCompilation(
@ -1045,7 +935,8 @@ SerializerForBackgroundCompilation::SerializerForBackgroundCompilation(
zone(), CompilationSubject(closure, broker_->isolate(), zone()))),
jump_target_environments_(zone()),
flags_(flags),
osr_offset_(osr_offset) {
osr_offset_(osr_offset),
arguments_(zone()) {
JSFunctionRef(broker, closure).Serialize();
}
@ -1063,7 +954,8 @@ SerializerForBackgroundCompilation::SerializerForBackgroundCompilation(
new_target, arguments, padding)),
jump_target_environments_(zone()),
flags_(flags),
osr_offset_(BailoutId::None()) {
osr_offset_(BailoutId::None()),
arguments_(arguments) {
TraceScope tracer(
broker_, this,
"SerializerForBackgroundCompilation::SerializerForBackgroundCompilation");
@ -1099,13 +991,15 @@ Hints SerializerForBackgroundCompilation::Run() {
<< broker()->zone()->allocation_size());
SharedFunctionInfoRef shared(broker(), environment()->function().shared());
FeedbackVectorRef feedback_vector_ref(broker(), feedback_vector());
if (shared.IsSerializedForCompilation(feedback_vector_ref)) {
if (!broker()->ShouldBeSerializedForCompilation(shared, feedback_vector_ref,
arguments_)) {
TRACE_BROKER(broker(), "Already ran serializer for SharedFunctionInfo "
<< Brief(*shared.object())
<< ", bailing out.\n");
return Hints();
}
shared.SetSerializedForCompilation(feedback_vector_ref);
broker()->SetSerializedForCompilation(shared, feedback_vector_ref,
arguments_);
// We eagerly call the {EnsureSourcePositionsAvailable} for all serialized
// SFIs while still on the main thread. Source positions will later be used

View File

@ -0,0 +1,143 @@
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file defines the hints classed gathered temporarily by the
// SerializerForBackgroundCompilation while it's analysing the bytecode
// and copying the necessary data to the JSHeapBroker for further usage
// by the reducers that run on the background thread.
#ifndef V8_COMPILER_SERIALIZER_HINTS_H_
#define V8_COMPILER_SERIALIZER_HINTS_H_
#include "src/compiler/functional-list.h"
#include "src/handles/handles.h"
#include "src/zone/zone-containers.h"
namespace v8 {
namespace internal {
class Context;
class Object;
class Map;
namespace compiler {
template <typename T, typename EqualTo>
class FunctionalSet {
public:
void Add(T const& elem, Zone* zone) {
for (auto const& l : data_) {
if (equal_to(l, elem)) return;
}
data_.PushFront(elem, zone);
}
bool Includes(FunctionalSet<T, EqualTo> const& other) const {
return std::all_of(other.begin(), other.end(), [&](T const& other_elem) {
return std::any_of(this->begin(), this->end(), [&](T const& this_elem) {
return equal_to(this_elem, other_elem);
});
});
}
bool IsEmpty() const { return data_.begin() == data_.end(); }
void Clear() { data_.Clear(); }
// Warning: quadratic time complexity.
bool operator==(const FunctionalSet<T, EqualTo>& other) const {
return this->data_.Size() == other.data_.Size() && this->Includes(other) &&
other.Includes(*this);
}
bool operator!=(const FunctionalSet<T, EqualTo>& other) const {
return !(*this == other);
}
using iterator = typename FunctionalList<T>::iterator;
iterator begin() const { return data_.begin(); }
iterator end() const { return data_.end(); }
private:
static EqualTo equal_to;
FunctionalList<T> data_;
};
template <typename T, typename EqualTo>
EqualTo FunctionalSet<T, EqualTo>::equal_to;
struct VirtualContext {
unsigned int distance;
Handle<Context> context;
VirtualContext(unsigned int distance_in, Handle<Context> context_in)
: distance(distance_in), context(context_in) {
CHECK_GT(distance, 0);
}
bool operator==(const VirtualContext& other) const {
return context.equals(other.context) && distance == other.distance;
}
};
class FunctionBlueprint;
struct VirtualBoundFunction;
using ConstantsSet = FunctionalSet<Handle<Object>, Handle<Object>::equal_to>;
using VirtualContextsSet =
FunctionalSet<VirtualContext, std::equal_to<VirtualContext>>;
using MapsSet = FunctionalSet<Handle<Map>, Handle<Map>::equal_to>;
using BlueprintsSet =
FunctionalSet<FunctionBlueprint, std::equal_to<FunctionBlueprint>>;
using BoundFunctionsSet =
FunctionalSet<VirtualBoundFunction, std::equal_to<VirtualBoundFunction>>;
class Hints {
public:
Hints() = default;
static Hints SingleConstant(Handle<Object> constant, Zone* zone);
const ConstantsSet& constants() const;
const MapsSet& maps() const;
const BlueprintsSet& function_blueprints() const;
const VirtualContextsSet& virtual_contexts() const;
const BoundFunctionsSet& virtual_bound_functions() const;
void AddConstant(Handle<Object> constant, Zone* zone);
void AddMap(Handle<Map> map, Zone* zone);
void AddFunctionBlueprint(FunctionBlueprint const& function_blueprint,
Zone* zone);
void AddVirtualContext(VirtualContext const& virtual_context, Zone* zone);
void AddVirtualBoundFunction(VirtualBoundFunction const& bound_function,
Zone* zone);
void Add(const Hints& other, Zone* zone);
void AddFromChildSerializer(const Hints& other, Zone* zone);
void Clear();
bool IsEmpty() const;
bool operator==(Hints const& other) const;
bool operator!=(Hints const& other) const { return !(*this == other); }
#ifdef ENABLE_SLOW_DCHECKS
bool Includes(Hints const& other) const;
bool Equals(Hints const& other) const;
#endif
private:
VirtualContextsSet virtual_contexts_;
ConstantsSet constants_;
MapsSet maps_;
BlueprintsSet function_blueprints_;
BoundFunctionsSet virtual_bound_functions_;
};
using HintsVector = ZoneVector<Hints>;
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_COMPILER_SERIALIZER_HINTS_H_

View File

@ -53,8 +53,8 @@ TEST(SerializeEmptyFunction) {
SerializerTester tester(
"function f() {}; %EnsureFeedbackVectorForFunction(f); return f;");
JSFunctionRef function = tester.function();
CHECK(
function.shared().IsSerializedForCompilation(function.feedback_vector()));
CHECK(tester.broker()->IsSerializedForCompilation(
function.shared(), function.feedback_vector()));
}
// This helper function allows for testing whether an inlinee candidate
@ -64,7 +64,8 @@ void CheckForSerializedInlinee(const char* source, int argc = 0,
Handle<Object> argv[] = {}) {
SerializerTester tester(source);
JSFunctionRef f = tester.function();
CHECK(f.shared().IsSerializedForCompilation(f.feedback_vector()));
CHECK(tester.broker()->IsSerializedForCompilation(f.shared(),
f.feedback_vector()));
MaybeHandle<Object> g_obj = Execution::Call(
tester.isolate(), tester.function().object(),
@ -81,7 +82,7 @@ void CheckForSerializedInlinee(const char* source, int argc = 0,
handle(g_func->shared(), tester.isolate()));
FeedbackVectorRef g_fv(tester.broker(),
handle(g_func->feedback_vector(), tester.isolate()));
CHECK(g_sfi.IsSerializedForCompilation(g_fv));
CHECK(tester.broker()->IsSerializedForCompilation(g_sfi, g_fv));
}
TEST(SerializeInlinedClosure) {
@ -348,6 +349,22 @@ TEST(BoundFunctionResult) {
"foo(); return foo;");
}
TEST(MultipleFunctionCalls) {
CheckForSerializedInlinee(
"function inc(x) { return ++x; }"
"function dec(x) { return --x; }"
"function apply(f, x) { return f(x); }"
"function foo() { apply(inc, 42); apply(dec, 42); return dec; }"
"%PrepareFunctionForOptimization(inc);"
"%PrepareFunctionForOptimization(dec);"
"%PrepareFunctionForOptimization(apply);"
"%PrepareFunctionForOptimization(foo);"
"foo();"
"foo();"
"%OptimizeFunctionOnNextCall(foo);"
"foo(); return foo;");
}
} // namespace compiler
} // namespace internal
} // namespace v8