[turbofan] Properly type field access to stable heap object maps.
Introduce new typing rules for LoadField[Map], which try to take into account stable map information if the object either has type Constant or type Class. If the map of the object is stable but can transition we have to introduce a code dependency in the Typer to make sure that the information (the Constant type we infer for LoadField[Map]) is valid (and stays valid). This also settles the policy for depending on map stability: The definition can introduce any number of maps, without having to pay attention to stability (i.e. you can always use Type::Class to introduce a map that is propagated along the value edges), and the use site is responsible for checking that the type information is valid before using it. I.e. if you use stable map information, you'll have to add a stability dependency (or make sure the map cannot transition). Drive-by-improvement: Add ReferenceEqualTyper which takes input types into account for improved constant folding. Drive-by-fix: Apply policy mentioned above to JSNativeContextSpecialization. R=jarin@chromium.org, rossberg@chromium.org BUG=v8:4470 LOG=n Review URL: https://codereview.chromium.org/1410953006 Cr-Commit-Position: refs/heads/master@{#31567}
This commit is contained in:
parent
15f36b2b1e
commit
44b9122d9f
@ -109,7 +109,10 @@ Reduction JSNativeContextSpecialization::ReduceJSLoadGlobal(Node* node) {
|
||||
property_cell_value_type = Type::Intersect(
|
||||
Type::Number(), Type::TaggedPointer(), graph()->zone());
|
||||
} else {
|
||||
property_cell_value_type = Type::Of(property_cell_value, graph()->zone());
|
||||
Handle<Map> property_cell_value_map(
|
||||
Handle<HeapObject>::cast(property_cell_value)->map(), isolate());
|
||||
property_cell_value_type =
|
||||
Type::Class(property_cell_value_map, graph()->zone());
|
||||
}
|
||||
Node* value = effect = graph()->NewNode(
|
||||
simplified()->LoadField(
|
||||
@ -407,17 +410,10 @@ bool JSNativeContextSpecialization::ComputePropertyAccessInfo(
|
||||
// TODO(bmeurer): It would be awesome to make this saner in the
|
||||
// runtime/GC interaction.
|
||||
field_type = Type::TaggedPointer();
|
||||
} else {
|
||||
} else if (!Type::Any()->Is(field_type)) {
|
||||
// Add proper code dependencies in case of stable field map(s).
|
||||
if (field_type->NumClasses() > 0 && field_type->NowStable()) {
|
||||
dependencies()->AssumeFieldType(
|
||||
handle(map->FindFieldOwner(number), isolate()));
|
||||
for (auto i = field_type->Classes(); !i.Done(); i.Advance()) {
|
||||
dependencies()->AssumeMapStable(i.Current());
|
||||
}
|
||||
} else {
|
||||
field_type = Type::TaggedPointer();
|
||||
}
|
||||
Handle<Map> field_owner_map(map->FindFieldOwner(number), isolate());
|
||||
dependencies()->AssumeFieldType(field_owner_map);
|
||||
}
|
||||
DCHECK(field_type->Is(Type::TaggedPointer()));
|
||||
}
|
||||
|
@ -1097,7 +1097,11 @@ Handle<Code> Pipeline::GenerateCode() {
|
||||
base::SmartPointer<Typer> typer;
|
||||
if (info()->is_typing_enabled()) {
|
||||
// Type the graph.
|
||||
typer.Reset(new Typer(isolate(), data.graph(), info()->function_type()));
|
||||
typer.Reset(new Typer(isolate(), data.graph(),
|
||||
info()->is_deoptimization_enabled()
|
||||
? Typer::kDeoptimizationEnabled
|
||||
: Typer::kNoFlags,
|
||||
info()->dependencies(), info()->function_type()));
|
||||
Run<TyperPhase>(typer.get());
|
||||
RunPrintAndVerify("Typed");
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "src/base/flags.h"
|
||||
#include "src/base/lazy-instance.h"
|
||||
#include "src/bootstrapper.h"
|
||||
#include "src/compilation-dependencies.h"
|
||||
#include "src/compiler/common-operator.h"
|
||||
#include "src/compiler/graph-reducer.h"
|
||||
#include "src/compiler/js-operator.h"
|
||||
@ -37,9 +38,13 @@ class Typer::Decorator final : public GraphDecorator {
|
||||
};
|
||||
|
||||
|
||||
Typer::Typer(Isolate* isolate, Graph* graph, Type::FunctionType* function_type)
|
||||
Typer::Typer(Isolate* isolate, Graph* graph, Flags flags,
|
||||
CompilationDependencies* dependencies,
|
||||
Type::FunctionType* function_type)
|
||||
: isolate_(isolate),
|
||||
graph_(graph),
|
||||
flags_(flags),
|
||||
dependencies_(dependencies),
|
||||
function_type_(function_type),
|
||||
decorator_(nullptr),
|
||||
cache_(kCache.Get()) {
|
||||
@ -204,6 +209,10 @@ class Typer::Visitor : public Reducer {
|
||||
Zone* zone() { return typer_->zone(); }
|
||||
Isolate* isolate() { return typer_->isolate(); }
|
||||
Graph* graph() { return typer_->graph(); }
|
||||
Typer::Flags flags() const { return typer_->flags(); }
|
||||
CompilationDependencies* dependencies() const {
|
||||
return typer_->dependencies();
|
||||
}
|
||||
|
||||
void SetWeakened(NodeId node_id) { weakened_nodes_.insert(node_id); }
|
||||
bool IsWeakened(NodeId node_id) {
|
||||
@ -252,6 +261,8 @@ class Typer::Visitor : public Reducer {
|
||||
static Type* JSLoadPropertyTyper(Type*, Type*, Typer*);
|
||||
static Type* JSCallFunctionTyper(Type*, Typer*);
|
||||
|
||||
static Type* ReferenceEqualTyper(Type*, Type*, Typer*);
|
||||
|
||||
Reduction UpdateType(Node* node, Type* current) {
|
||||
if (NodeProperties::IsTyped(node)) {
|
||||
// Widen the type of a previously typed node.
|
||||
@ -1575,8 +1586,17 @@ Type* Typer::Visitor::TypePlainPrimitiveToNumber(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
Type* Typer::Visitor::ReferenceEqualTyper(Type* lhs, Type* rhs, Typer* t) {
|
||||
if (lhs->IsConstant() && rhs->Is(lhs)) {
|
||||
return t->singleton_true_;
|
||||
}
|
||||
return Type::Boolean();
|
||||
}
|
||||
|
||||
|
||||
Type* Typer::Visitor::TypeReferenceEqual(Node* node) {
|
||||
return Type::Boolean(zone());
|
||||
return TypeBinaryOp(node, ReferenceEqualTyper);
|
||||
}
|
||||
|
||||
|
||||
@ -1666,8 +1686,53 @@ Type* Typer::Visitor::TypeChangeBitToBool(Node* node) {
|
||||
Type* Typer::Visitor::TypeAllocate(Node* node) { return Type::TaggedPointer(); }
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
MaybeHandle<Map> GetStableMapFromObjectType(Type* object_type) {
|
||||
if (object_type->IsConstant() &&
|
||||
object_type->AsConstant()->Value()->IsHeapObject()) {
|
||||
Handle<Map> object_map(
|
||||
Handle<HeapObject>::cast(object_type->AsConstant()->Value())->map());
|
||||
if (object_map->is_stable()) return object_map;
|
||||
} else if (object_type->IsClass()) {
|
||||
Handle<Map> object_map = object_type->AsClass()->Map();
|
||||
if (object_map->is_stable()) return object_map;
|
||||
}
|
||||
return MaybeHandle<Map>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
Type* Typer::Visitor::TypeLoadField(Node* node) {
|
||||
return FieldAccessOf(node->op()).type;
|
||||
FieldAccess const& access = FieldAccessOf(node->op());
|
||||
if (access.base_is_tagged == kTaggedBase &&
|
||||
access.offset == HeapObject::kMapOffset) {
|
||||
// The type of LoadField[Map](o) is Constant(map) if map is stable and
|
||||
// either
|
||||
// (a) o has type Constant(object) and map == object->map, or
|
||||
// (b) o has type Class(map),
|
||||
// and either
|
||||
// (1) map cannot transition further, or
|
||||
// (2) deoptimization is enabled and we can add a code dependency on the
|
||||
// stability of map (to guard the Constant type information).
|
||||
Type* const object = Operand(node, 0);
|
||||
if (object->Is(Type::None())) return Type::None();
|
||||
Handle<Map> object_map;
|
||||
if (GetStableMapFromObjectType(object).ToHandle(&object_map)) {
|
||||
if (object_map->CanTransition()) {
|
||||
if (flags() & kDeoptimizationEnabled) {
|
||||
dependencies()->AssumeMapStable(object_map);
|
||||
} else {
|
||||
return access.type;
|
||||
}
|
||||
}
|
||||
Type* object_map_type = Type::Constant(object_map, zone());
|
||||
DCHECK(object_map_type->Is(access.type));
|
||||
return object_map_type;
|
||||
}
|
||||
}
|
||||
return access.type;
|
||||
}
|
||||
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
#ifndef V8_COMPILER_TYPER_H_
|
||||
#define V8_COMPILER_TYPER_H_
|
||||
|
||||
#include "src/base/flags.h"
|
||||
#include "src/compiler/graph.h"
|
||||
#include "src/types.h"
|
||||
|
||||
@ -12,6 +13,7 @@ namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
// Forward declarations.
|
||||
class CompilationDependencies;
|
||||
class ZoneTypeCache;
|
||||
|
||||
namespace compiler {
|
||||
@ -19,7 +21,15 @@ namespace compiler {
|
||||
|
||||
class Typer {
|
||||
public:
|
||||
Typer(Isolate* isolate, Graph* graph,
|
||||
// Flags that control the mode of operation.
|
||||
enum Flag {
|
||||
kNoFlags = 0u,
|
||||
kDeoptimizationEnabled = 1u << 0,
|
||||
};
|
||||
typedef base::Flags<Flag> Flags;
|
||||
|
||||
Typer(Isolate* isolate, Graph* graph, Flags flags = kNoFlags,
|
||||
CompilationDependencies* dependencies = nullptr,
|
||||
Type::FunctionType* function_type = nullptr);
|
||||
~Typer();
|
||||
|
||||
@ -34,10 +44,14 @@ class Typer {
|
||||
Graph* graph() const { return graph_; }
|
||||
Zone* zone() const { return graph()->zone(); }
|
||||
Isolate* isolate() const { return isolate_; }
|
||||
Flags flags() const { return flags_; }
|
||||
CompilationDependencies* dependencies() const { return dependencies_; }
|
||||
Type::FunctionType* function_type() const { return function_type_; }
|
||||
|
||||
Isolate* const isolate_;
|
||||
Graph* const graph_;
|
||||
Flags const flags_;
|
||||
CompilationDependencies* const dependencies_;
|
||||
Type::FunctionType* function_type_;
|
||||
Decorator* decorator_;
|
||||
ZoneTypeCache const& cache_;
|
||||
@ -52,6 +66,8 @@ class Typer {
|
||||
DISALLOW_COPY_AND_ASSIGN(Typer);
|
||||
};
|
||||
|
||||
DEFINE_OPERATORS_FOR_FLAGS(Typer::Flags)
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -176,6 +176,7 @@ class FunctionTester : public InitializedHandleScope {
|
||||
Pipeline pipeline(&info);
|
||||
Handle<Code> code = pipeline.GenerateCode();
|
||||
CHECK(!code.is_null());
|
||||
info.dependencies()->Commit(code);
|
||||
info.context()->native_context()->AddOptimizedCode(*code);
|
||||
function->ReplaceCode(*code);
|
||||
return function;
|
||||
|
Loading…
Reference in New Issue
Block a user