Commit e1088b27 authored by bmeurer's avatar bmeurer Committed by Commit bot

[turbofan] Initial support for monomorphic/polymorphic property loads.

Native context specialization now lowers monomorphic and
polymorphic accesses to data and constant data properties on
object and/or prototype chain. We don't deal with accessors
yet, and we also completely ignore proxies (which is compatible
with what Crankshaft does).

The code is more or less the straightforward implementation. We
will need to refactor that and extract common patterns once the
remaining bits for full load/store support is in.

CQ_INCLUDE_TRYBOTS=tryserver.v8:v8_linux_nosnap_rel
R=jarin@chromium.org
BUG=v8:4470
LOG=n

Committed: https://crrev.com/3a0bf860b7177f7abef01ff308a53603389d958e
Cr-Commit-Position: refs/heads/master@{#31340}

Review URL: https://codereview.chromium.org/1396333010

Cr-Commit-Position: refs/heads/master@{#31352}
parent fc4da977
......@@ -106,6 +106,15 @@ void CompilationDependencies::Rollback() {
}
void CompilationDependencies::AssumeMapStable(Handle<Map> map) {
DCHECK(map->is_stable());
// Do nothing if the map cannot transition.
if (map->CanTransition()) {
Insert(DependentCode::kPrototypeCheckGroup, map);
}
}
void CompilationDependencies::AssumeTransitionStable(
Handle<AllocationSite> site) {
// Do nothing if the object doesn't have any useful element transitions left.
......
......@@ -31,6 +31,7 @@ class CompilationDependencies {
void AssumeFieldType(Handle<Map> map) {
Insert(DependentCode::kFieldTypeGroup, map);
}
void AssumeMapStable(Handle<Map> map);
void AssumePropertyCell(Handle<PropertyCell> cell) {
Insert(DependentCode::kPropertyCellChangedGroup, cell);
}
......
......@@ -9,8 +9,10 @@
#include "src/compiler/js-graph.h"
#include "src/compiler/js-operator.h"
#include "src/contexts.h"
#include "src/field-index-inl.h"
#include "src/lookup.h"
#include "src/objects-inl.h" // TODO(mstarzinger): Temporary cycle breaker!
#include "src/type-feedback-vector.h"
namespace v8 {
namespace internal {
......@@ -25,12 +27,14 @@ struct JSGlobalSpecialization::ScriptContextTableLookupResult {
JSGlobalSpecialization::JSGlobalSpecialization(
Editor* editor, JSGraph* jsgraph, Flags flags,
Handle<GlobalObject> global_object, CompilationDependencies* dependencies)
Handle<GlobalObject> global_object, CompilationDependencies* dependencies,
Zone* zone)
: AdvancedReducer(editor),
jsgraph_(jsgraph),
flags_(flags),
global_object_(global_object),
dependencies_(dependencies) {}
dependencies_(dependencies),
zone_(zone) {}
Reduction JSGlobalSpecialization::Reduce(Node* node) {
......@@ -39,6 +43,8 @@ Reduction JSGlobalSpecialization::Reduce(Node* node) {
return ReduceJSLoadGlobal(node);
case IrOpcode::kJSStoreGlobal:
return ReduceJSStoreGlobal(node);
case IrOpcode::kJSLoadNamed:
return ReduceJSLoadNamed(node);
default:
break;
}
......@@ -230,11 +236,375 @@ Reduction JSGlobalSpecialization::ReduceJSStoreGlobal(Node* node) {
}
Reduction JSGlobalSpecialization::Replace(Node* node, Handle<Object> value) {
// TODO(bmeurer): Move this to JSGraph::HeapConstant instead?
if (value->IsConsString()) {
value = String::Flatten(Handle<String>::cast(value), TENURED);
// This class encapsulates all information required to access a certain
// object property, either on the object itself or on the prototype chain.
class JSGlobalSpecialization::PropertyAccessInfo final {
public:
enum Kind { kInvalid, kData, kDataConstant };
static PropertyAccessInfo DataConstant(Type* receiver_type,
Handle<Object> constant,
MaybeHandle<JSObject> holder) {
return PropertyAccessInfo(holder, constant, receiver_type);
}
static PropertyAccessInfo Data(Type* receiver_type, FieldIndex field_index,
Representation field_representation,
MaybeHandle<JSObject> holder) {
return PropertyAccessInfo(holder, field_index, field_representation,
receiver_type);
}
PropertyAccessInfo() : kind_(kInvalid) {}
PropertyAccessInfo(MaybeHandle<JSObject> holder, Handle<Object> constant,
Type* receiver_type)
: kind_(kDataConstant),
receiver_type_(receiver_type),
constant_(constant),
holder_(holder) {}
PropertyAccessInfo(MaybeHandle<JSObject> holder, FieldIndex field_index,
Representation field_representation, Type* receiver_type)
: kind_(kData),
receiver_type_(receiver_type),
holder_(holder),
field_index_(field_index),
field_representation_(field_representation) {}
bool IsDataConstant() const { return kind() == kDataConstant; }
bool IsData() const { return kind() == kData; }
Kind kind() const { return kind_; }
MaybeHandle<JSObject> holder() const { return holder_; }
Handle<Object> constant() const { return constant_; }
FieldIndex field_index() const { return field_index_; }
Representation field_representation() const { return field_representation_; }
Type* receiver_type() const { return receiver_type_; }
private:
Kind kind_;
Type* receiver_type_;
Handle<Object> constant_;
MaybeHandle<JSObject> holder_;
FieldIndex field_index_;
Representation field_representation_;
};
namespace {
bool CanInlinePropertyAccess(Handle<Map> map) {
// TODO(bmeurer): Do something about the number stuff.
if (map->instance_type() == HEAP_NUMBER_TYPE) return false;
if (map->instance_type() < FIRST_NONSTRING_TYPE) return true;
return map->IsJSObjectMap() && !map->is_dictionary_map() &&
!map->has_named_interceptor() &&
// TODO(verwaest): Whitelist contexts to which we have access.
!map->is_access_check_needed();
}
} // namespace
bool JSGlobalSpecialization::ComputePropertyAccessInfo(
Handle<Map> map, Handle<Name> name, PropertyAccessInfo* access_info) {
MaybeHandle<JSObject> holder;
Type* receiver_type = Type::Class(map, graph()->zone());
while (CanInlinePropertyAccess(map)) {
// Lookup the named property on the {map}.
Handle<DescriptorArray> descriptors(map->instance_descriptors(), isolate());
int const number = descriptors->SearchWithCache(*name, *map);
if (number != DescriptorArray::kNotFound) {
PropertyDetails const details = descriptors->GetDetails(number);
if (details.type() == DATA_CONSTANT) {
*access_info = PropertyAccessInfo::DataConstant(
receiver_type, handle(descriptors->GetValue(number), isolate()),
holder);
return true;
} else if (details.type() == DATA) {
int index = descriptors->GetFieldIndex(number);
Representation field_representation = details.representation();
FieldIndex field_index = FieldIndex::ForPropertyIndex(
*map, index, field_representation.IsDouble());
*access_info = PropertyAccessInfo::Data(receiver_type, field_index,
field_representation, holder);
return true;
} else {
// TODO(bmeurer): Add support for accessors.
break;
}
}
// Don't search on the prototype chain for special indices in case of
// integer indexed exotic objects (see ES6 section 9.4.5).
if (map->IsJSTypedArrayMap() && name->IsString() &&
IsSpecialIndex(isolate()->unicode_cache(), String::cast(*name))) {
break;
}
// Walk up the prototype chain.
if (!map->prototype()->IsJSObject()) {
// TODO(bmeurer): Handle the not found case if the prototype is null.
break;
}
Handle<JSObject> map_prototype(JSObject::cast(map->prototype()), isolate());
if (map_prototype->map()->is_deprecated()) {
// Try to migrate the prototype object so we don't embed the deprecated
// map into the optimized code.
JSObject::TryMigrateInstance(map_prototype);
}
map = handle(map_prototype->map(), isolate());
holder = map_prototype;
}
return false;
}
bool JSGlobalSpecialization::ComputePropertyAccessInfos(
MapHandleList const& maps, Handle<Name> name,
ZoneVector<PropertyAccessInfo>* access_infos) {
for (Handle<Map> map : maps) {
PropertyAccessInfo access_info;
if (!ComputePropertyAccessInfo(map, name, &access_info)) return false;
access_infos->push_back(access_info);
}
return true;
}
Reduction JSGlobalSpecialization::ReduceJSLoadNamed(Node* node) {
DCHECK_EQ(IrOpcode::kJSLoadNamed, node->opcode());
LoadNamedParameters const p = LoadNamedParametersOf(node->op());
Handle<Name> name = p.name();
Node* receiver = NodeProperties::GetValueInput(node, 0);
Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Not much we can do if deoptimization support is disabled.
if (!(flags() & kDeoptimizationEnabled)) return NoChange();
// Extract receiver maps from the LOAD_IC using the LoadICNexus.
MapHandleList receiver_maps;
if (!p.feedback().IsValid()) return NoChange();
LoadICNexus nexus(p.feedback().vector(), p.feedback().slot());
if (nexus.ExtractMaps(&receiver_maps) == 0) return NoChange();
DCHECK_LT(0, receiver_maps.length());
// Compute property access infos for the receiver maps.
ZoneVector<PropertyAccessInfo> access_infos(zone());
if (!ComputePropertyAccessInfos(receiver_maps, name, &access_infos)) {
return NoChange();
}
DCHECK(!access_infos.empty());
// The final states for every polymorphic branch. We join them with
// Merge+Phi+EffectPhi at the bottom.
ZoneVector<Node*> values(zone());
ZoneVector<Node*> effects(zone());
ZoneVector<Node*> controls(zone());
// The list of "exiting" controls, which currently go to a single deoptimize.
// TODO(bmeurer): Consider using an IC as fallback.
Node* const exit_effect = effect;
ZoneVector<Node*> exit_controls(zone());
// Ensure that {receiver} is a heap object.
Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), receiver);
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
exit_controls.push_back(graph()->NewNode(common()->IfTrue(), branch));
control = graph()->NewNode(common()->IfFalse(), branch);
// Load the {receiver} map. The resulting effect is the dominating effect for
// all (polymorphic) branches.
Node* receiver_map = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
receiver, effect, control);
// Generate code for the various different property access patterns.
Node* fallthrough_control = control;
for (PropertyAccessInfo const& access_info : access_infos) {
Node* this_value = receiver;
Node* this_effect = effect;
Node* this_control;
// Perform map check on {receiver}.
Type* receiver_type = access_info.receiver_type();
if (receiver_type->Is(Type::String())) {
// Emit an instance type check for strings.
Node* receiver_instance_type = this_effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForMapInstanceType()),
receiver_map, this_effect, fallthrough_control);
Node* check =
graph()->NewNode(machine()->Uint32LessThan(), receiver_instance_type,
jsgraph()->Uint32Constant(FIRST_NONSTRING_TYPE));
Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
check, fallthrough_control);
fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
this_control = graph()->NewNode(common()->IfTrue(), branch);
} else {
// Emit a (sequence of) map checks for other properties.
ZoneVector<Node*> this_controls(zone());
for (auto i = access_info.receiver_type()->Classes(); !i.Done();
i.Advance()) {
Handle<Map> map = i.Current();
Node* check =
graph()->NewNode(simplified()->ReferenceEqual(Type::Internal()),
receiver_map, jsgraph()->Constant(map));
Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
check, fallthrough_control);
this_controls.push_back(graph()->NewNode(common()->IfTrue(), branch));
fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
}
int const this_control_count = static_cast<int>(this_controls.size());
this_control =
(this_control_count == 1)
? this_controls.front()
: graph()->NewNode(common()->Merge(this_control_count),
this_control_count, &this_controls.front());
}
// Determine actual holder and perform prototype chain checks.
Handle<JSObject> holder;
if (access_info.holder().ToHandle(&holder)) {
this_value = jsgraph()->Constant(holder);
for (auto i = access_info.receiver_type()->Classes(); !i.Done();
i.Advance()) {
Handle<Map> map = i.Current();
PrototypeIterator j(map);
while (true) {
// Check that the {prototype} still has the same map. For stable
// maps, we can add a stability dependency on the prototype map;
// for everything else we need to perform a map check at runtime.
Handle<JSReceiver> prototype =
PrototypeIterator::GetCurrent<JSReceiver>(j);
if (prototype->map()->is_stable()) {
dependencies()->AssumeMapStable(
handle(prototype->map(), isolate()));
} else {
Node* prototype_map = this_effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForMap()),
jsgraph()->Constant(prototype), this_effect, this_control);
Node* check = graph()->NewNode(
simplified()->ReferenceEqual(Type::Internal()), prototype_map,
jsgraph()->Constant(handle(prototype->map(), isolate())));
Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
check, this_control);
exit_controls.push_back(
graph()->NewNode(common()->IfFalse(), branch));
this_control = graph()->NewNode(common()->IfTrue(), branch);
}
// Stop once we get to the holder.
if (prototype.is_identical_to(holder)) break;
j.Advance();
}
}
}
// Generate the actual property access.
if (access_info.IsDataConstant()) {
this_value = jsgraph()->Constant(access_info.constant());
} else {
// TODO(bmeurer): This is sort of adhoc, and must be refactored into some
// common code once we also have support for stores.
DCHECK(access_info.IsData());
FieldIndex const field_index = access_info.field_index();
Representation const field_representation =
access_info.field_representation();
if (!field_index.is_inobject()) {
this_value = this_effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectProperties()),
this_value, this_effect, this_control);
}
FieldAccess field_access;
field_access.base_is_tagged = kTaggedBase;
field_access.offset = field_index.offset();
field_access.name = name;
field_access.type = Type::Any();
field_access.machine_type = kMachAnyTagged;
if (field_representation.IsSmi()) {
field_access.type = Type::Intersect(
Type::SignedSmall(), Type::TaggedSigned(), graph()->zone());
} else if (field_representation.IsDouble()) {
if (!field_index.is_inobject() || field_index.is_hidden_field() ||
!FLAG_unbox_double_fields) {
this_value = this_effect =
graph()->NewNode(simplified()->LoadField(field_access),
this_value, this_effect, this_control);
field_access.offset = HeapNumber::kValueOffset;
field_access.name = MaybeHandle<Name>();
}
field_access.type = Type::Intersect(
Type::Number(), Type::UntaggedFloat64(), graph()->zone());
field_access.machine_type = kMachFloat64;
} else if (field_representation.IsHeapObject()) {
field_access.type = Type::TaggedPointer();
}
this_value = this_effect =
graph()->NewNode(simplified()->LoadField(field_access), this_value,
this_effect, this_control);
}
// Remember the final state for this property access.
values.push_back(this_value);
effects.push_back(this_effect);
controls.push_back(this_control);
}
// Collect the fallthru control as final "exit" control.
exit_controls.push_back(fallthrough_control);
// TODO(bmeurer/mtrofin): Splintering cannot currently deal with deferred
// blocks that contain only a single non-deoptimize instruction (i.e. a
// jump). Generating a single Merge here, which joins all the deoptimizing
// controls would generate a lot of these basic blocks, however. So this
// is disabled for now until splintering is fixed.
#if 0
// Generate the single "exit" point, where we get if either all map/instance
// type checks failed, or one of the assumptions inside one of the cases
// failes (i.e. failing prototype chain check).
// TODO(bmeurer): Consider falling back to IC here if deoptimization is
// disabled.
int const exit_control_count = static_cast<int>(exit_controls.size());
Node* exit_control =
(exit_control_count == 1)
? exit_controls.front()
: graph()->NewNode(common()->Merge(exit_control_count),
exit_control_count, &exit_controls.front());
Node* deoptimize = graph()->NewNode(common()->Deoptimize(), frame_state,
exit_effect, exit_control);
// TODO(bmeurer): This should be on the AdvancedReducer somehow.
NodeProperties::MergeControlToEnd(graph(), common(), deoptimize);
#else
for (Node* const exit_control : exit_controls) {
Node* deoptimize = graph()->NewNode(common()->Deoptimize(), frame_state,
exit_effect, exit_control);
// TODO(bmeurer): This should be on the AdvancedReducer somehow.
NodeProperties::MergeControlToEnd(graph(), common(), deoptimize);
}
#endif
// Generate the final merge point for all (polymorphic) branches.
Node* value;
int const control_count = static_cast<int>(controls.size());
if (control_count == 1) {
value = values.front();
effect = effects.front();
control = controls.front();
} else {
control = graph()->NewNode(common()->Merge(control_count), control_count,
&controls.front());
values.push_back(control);
value = graph()->NewNode(common()->Phi(kMachAnyTagged, control_count),
control_count + 1, &values.front());
effects.push_back(control);
effect = graph()->NewNode(common()->EffectPhi(control_count),
control_count + 1, &effects.front());
}
return Replace(node, value, effect, control);
}
Reduction JSGlobalSpecialization::Replace(Node* node, Handle<Object> value) {
return Replace(node, jsgraph()->Constant(value));
}
......@@ -267,6 +637,11 @@ Isolate* JSGlobalSpecialization::isolate() const {
}
MachineOperatorBuilder* JSGlobalSpecialization::machine() const {
return jsgraph()->machine();
}
CommonOperatorBuilder* JSGlobalSpecialization::common() const {
return jsgraph()->common();
}
......
......@@ -22,6 +22,7 @@ namespace compiler {
class CommonOperatorBuilder;
class JSGraph;
class JSOperatorBuilder;
class MachineOperatorBuilder;
// Specializes a given JSGraph to a given GlobalObject, potentially constant
......@@ -38,13 +39,14 @@ class JSGlobalSpecialization final : public AdvancedReducer {
JSGlobalSpecialization(Editor* editor, JSGraph* jsgraph, Flags flags,
Handle<GlobalObject> global_object,
CompilationDependencies* dependencies);
CompilationDependencies* dependencies, Zone* zone);
Reduction Reduce(Node* node) final;
private:
Reduction ReduceJSLoadGlobal(Node* node);
Reduction ReduceJSStoreGlobal(Node* node);
Reduction ReduceJSLoadNamed(Node* node);
Reduction Replace(Node* node, Node* value, Node* effect = nullptr,
Node* control = nullptr) {
......@@ -53,6 +55,12 @@ class JSGlobalSpecialization final : public AdvancedReducer {
}
Reduction Replace(Node* node, Handle<Object> value);
class PropertyAccessInfo;
bool ComputePropertyAccessInfo(Handle<Map> map, Handle<Name> name,
PropertyAccessInfo* access_info);
bool ComputePropertyAccessInfos(MapHandleList const& maps, Handle<Name> name,
ZoneVector<PropertyAccessInfo>* access_infos);
struct ScriptContextTableLookupResult;
bool LookupInScriptContextTable(Handle<Name> name,
ScriptContextTableLookupResult* result);
......@@ -63,14 +71,17 @@ class JSGlobalSpecialization final : public AdvancedReducer {
CommonOperatorBuilder* common() const;
JSOperatorBuilder* javascript() const;
SimplifiedOperatorBuilder* simplified() const;
MachineOperatorBuilder* machine() const;
Flags flags() const { return flags_; }
Handle<GlobalObject> global_object() const { return global_object_; }
CompilationDependencies* dependencies() const { return dependencies_; }
Zone* zone() const { return zone_; }
JSGraph* const jsgraph_;
Flags const flags_;
Handle<GlobalObject> global_object_;
CompilationDependencies* const dependencies_;
Zone* const zone_;
DISALLOW_COPY_AND_ASSIGN(JSGlobalSpecialization);
};
......
......@@ -11,8 +11,11 @@ namespace v8 {
namespace internal {
namespace compiler {
Node* JSGraph::ImmovableHeapConstant(Handle<HeapObject> object) {
return graph()->NewNode(common()->HeapConstant(object));
Node* JSGraph::ImmovableHeapConstant(Handle<HeapObject> value) {
if (value->IsConsString()) {
value = String::Flatten(Handle<String>::cast(value), TENURED);
}
return graph()->NewNode(common()->HeapConstant(value));
}
......@@ -78,7 +81,7 @@ Node* JSGraph::HeapConstant(Handle<HeapObject> value) {
// TODO(titzer): We could also match against the addresses of immortable
// immovables here, even without access to the heap, thus always
// canonicalizing references to them.
return graph()->NewNode(common()->HeapConstant(value));
return ImmovableHeapConstant(value);
}
......
......@@ -369,7 +369,8 @@ Reduction JSInliner::ReduceJSCallFunction(Node* node,
info.is_deoptimization_enabled()
? JSGlobalSpecialization::kDeoptimizationEnabled
: JSGlobalSpecialization::kNoFlags,
handle(info.global_object(), info.isolate()), info_->dependencies());
handle(info.global_object(), info.isolate()), info_->dependencies(),
local_zone_);
graph_reducer.AddReducer(&dead_code_elimination);
graph_reducer.AddReducer(&common_reducer);
graph_reducer.AddReducer(&global_specialization);
......
......@@ -519,7 +519,7 @@ struct NativeContextSpecializationPhase {
? JSGlobalSpecialization::kDeoptimizationEnabled
: JSGlobalSpecialization::kNoFlags,
handle(data->info()->global_object(), data->isolate()),
data->info()->dependencies());
data->info()->dependencies(), temp_zone);
AddReducer(data, &graph_reducer, &dead_code_elimination);
AddReducer(data, &graph_reducer, &common_reducer);
AddReducer(data, &graph_reducer, &global_specialization);
......
......@@ -19,6 +19,8 @@ class Map;
// index it was originally generated from.
class FieldIndex final {
public:
FieldIndex() : bit_field_(0) {}
static FieldIndex ForPropertyIndex(Map* map,
int index,
bool is_double = false);
......
......@@ -4905,6 +4905,7 @@ bool Map::IsJSGlobalProxyMap() {
bool Map::IsJSGlobalObjectMap() {
return instance_type() == JS_GLOBAL_OBJECT_TYPE;
}
bool Map::IsJSTypedArrayMap() { return instance_type() == JS_TYPED_ARRAY_TYPE; }
bool Map::IsGlobalObjectMap() {
const InstanceType type = instance_type();
return type == JS_GLOBAL_OBJECT_TYPE || type == JS_BUILTINS_OBJECT_TYPE;
......
......@@ -5878,6 +5878,7 @@ class Map: public HeapObject {
inline bool IsJSProxyMap();
inline bool IsJSGlobalProxyMap();
inline bool IsJSGlobalObjectMap();
inline bool IsJSTypedArrayMap();
inline bool IsGlobalObjectMap();
inline bool CanOmitMapChecks();
......
......@@ -90,7 +90,6 @@ function f3(one) {
for (var j = 0; j < 5; ++j) f3(1);
%OptimizeFunctionOnNextCall(f3);
f3(1);
assertTrue(%GetOptimizationStatus(f3) != 2);
......
......@@ -88,7 +88,6 @@ function f3(one) {
for (var j = 0; j < 5; ++j) f3(1);
%OptimizeFunctionOnNextCall(f3);
f3(1);
assertTrue(%GetOptimizationStatus(f3) != 2);
......
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