blob: bead0618f69b3c82ee480fd1ddc6620a69f21fc3 [file] [log] [blame]
// Copyright 2017 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.
#include "src/compiler/property-access-builder.h"
#include "src/compilation-dependencies.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/access-info.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/simplified-operator.h"
#include "src/lookup.h"
#include "src/field-index-inl.h"
#include "src/isolate-inl.h"
namespace v8 {
namespace internal {
namespace compiler {
Graph* PropertyAccessBuilder::graph() const { return jsgraph()->graph(); }
Isolate* PropertyAccessBuilder::isolate() const { return jsgraph()->isolate(); }
CommonOperatorBuilder* PropertyAccessBuilder::common() const {
return jsgraph()->common();
}
SimplifiedOperatorBuilder* PropertyAccessBuilder::simplified() const {
return jsgraph()->simplified();
}
bool HasOnlyStringMaps(MapHandles const& maps) {
for (auto map : maps) {
if (!map->IsStringMap()) return false;
}
return true;
}
namespace {
bool HasOnlyNumberMaps(MapHandles const& maps) {
for (auto map : maps) {
if (map->instance_type() != HEAP_NUMBER_TYPE) return false;
}
return true;
}
bool HasOnlySequentialStringMaps(MapHandles const& maps) {
for (auto map : maps) {
if (!map->IsStringMap()) return false;
if (!StringShape(map->instance_type()).IsSequential()) {
return false;
}
}
return true;
}
} // namespace
bool PropertyAccessBuilder::TryBuildStringCheck(MapHandles const& maps,
Node** receiver, Node** effect,
Node* control) {
if (HasOnlyStringMaps(maps)) {
if (HasOnlySequentialStringMaps(maps)) {
*receiver = *effect = graph()->NewNode(simplified()->CheckSeqString(),
*receiver, *effect, control);
} else {
// Monormorphic string access (ignoring the fact that there are multiple
// String maps).
*receiver = *effect =
graph()->NewNode(simplified()->CheckString(VectorSlotPair()),
*receiver, *effect, control);
}
return true;
}
return false;
}
bool PropertyAccessBuilder::TryBuildNumberCheck(MapHandles const& maps,
Node** receiver, Node** effect,
Node* control) {
if (HasOnlyNumberMaps(maps)) {
// Monomorphic number access (we also deal with Smis here).
*receiver = *effect =
graph()->NewNode(simplified()->CheckNumber(VectorSlotPair()), *receiver,
*effect, control);
return true;
}
return false;
}
namespace {
bool NeedsCheckHeapObject(Node* receiver) {
switch (receiver->opcode()) {
case IrOpcode::kConvertReceiver:
case IrOpcode::kHeapConstant:
case IrOpcode::kJSCreate:
case IrOpcode::kJSCreateArguments:
case IrOpcode::kJSCreateArray:
case IrOpcode::kJSCreateClosure:
case IrOpcode::kJSCreateIterResultObject:
case IrOpcode::kJSCreateLiteralArray:
case IrOpcode::kJSCreateEmptyLiteralArray:
case IrOpcode::kJSCreateLiteralObject:
case IrOpcode::kJSCreateEmptyLiteralObject:
case IrOpcode::kJSCreateLiteralRegExp:
case IrOpcode::kJSCreateGeneratorObject:
case IrOpcode::kJSConstructForwardVarargs:
case IrOpcode::kJSConstruct:
case IrOpcode::kJSConstructWithArrayLike:
case IrOpcode::kJSConstructWithSpread:
case IrOpcode::kJSToName:
case IrOpcode::kJSToString:
case IrOpcode::kJSToObject:
case IrOpcode::kTypeOf:
case IrOpcode::kJSGetSuperConstructor:
return false;
case IrOpcode::kPhi: {
Node* control = NodeProperties::GetControlInput(receiver);
if (control->opcode() != IrOpcode::kMerge) return true;
for (int i = 0; i < receiver->InputCount() - 1; ++i) {
if (NeedsCheckHeapObject(receiver->InputAt(i))) return true;
}
return false;
}
default:
return true;
}
}
} // namespace
Node* PropertyAccessBuilder::BuildCheckHeapObject(Node* receiver, Node** effect,
Node* control) {
if (NeedsCheckHeapObject(receiver)) {
receiver = *effect = graph()->NewNode(simplified()->CheckHeapObject(),
receiver, *effect, control);
}
return receiver;
}
void PropertyAccessBuilder::BuildCheckMaps(
Node* receiver, Node** effect, Node* control,
std::vector<Handle<Map>> const& receiver_maps) {
HeapObjectMatcher m(receiver);
if (m.HasValue()) {
Handle<Map> receiver_map(m.Value()->map(), isolate());
if (receiver_map->is_stable()) {
for (Handle<Map> map : receiver_maps) {
if (map.is_identical_to(receiver_map)) {
dependencies()->AssumeMapStable(receiver_map);
return;
}
}
}
}
ZoneHandleSet<Map> maps;
CheckMapsFlags flags = CheckMapsFlag::kNone;
for (Handle<Map> map : receiver_maps) {
maps.insert(map, graph()->zone());
if (map->is_migration_target()) {
flags |= CheckMapsFlag::kTryMigrateInstance;
}
}
*effect = graph()->NewNode(simplified()->CheckMaps(flags, maps), receiver,
*effect, control);
}
Node* PropertyAccessBuilder::BuildCheckValue(Node* receiver, Node** effect,
Node* control,
Handle<HeapObject> value) {
HeapObjectMatcher m(receiver);
if (m.Is(value)) return receiver;
Node* expected = jsgraph()->HeapConstant(value);
Node* check =
graph()->NewNode(simplified()->ReferenceEqual(), receiver, expected);
*effect =
graph()->NewNode(simplified()->CheckIf(DeoptimizeReason::kWrongValue),
check, *effect, control);
return expected;
}
void PropertyAccessBuilder::AssumePrototypesStable(
Handle<Context> native_context,
std::vector<Handle<Map>> const& receiver_maps, Handle<JSObject> holder) {
// Determine actual holder and perform prototype chain checks.
for (auto map : receiver_maps) {
// Perform the implicit ToObject for primitives here.
// Implemented according to ES6 section 7.3.2 GetV (V, P).
Handle<JSFunction> constructor;
if (Map::GetConstructorFunction(map, native_context)
.ToHandle(&constructor)) {
map = handle(constructor->initial_map(), holder->GetIsolate());
}
dependencies()->AssumePrototypeMapsStable(map, holder);
}
}
Node* PropertyAccessBuilder::ResolveHolder(
PropertyAccessInfo const& access_info, Node* receiver) {
Handle<JSObject> holder;
if (access_info.holder().ToHandle(&holder)) {
return jsgraph()->Constant(holder);
}
return receiver;
}
Node* PropertyAccessBuilder::TryBuildLoadConstantDataField(
Handle<Name> name, PropertyAccessInfo const& access_info, Node* receiver) {
// Optimize immutable property loads.
HeapObjectMatcher m(receiver);
if (m.HasValue() && m.Value()->IsJSObject()) {
// TODO(ishell): Use something simpler like
//
// Handle<Object> value =
// JSObject::FastPropertyAt(Handle<JSObject>::cast(m.Value()),
// Representation::Tagged(), field_index);
//
// here, once we have the immutable bit in the access_info.
// TODO(turbofan): Given that we already have the field_index here, we
// might be smarter in the future and not rely on the LookupIterator,
// but for now let's just do what Crankshaft does.
LookupIterator it(m.Value(), name, LookupIterator::OWN_SKIP_INTERCEPTOR);
if (it.state() == LookupIterator::DATA) {
bool is_reaonly_non_configurable =
it.IsReadOnly() && !it.IsConfigurable();
if (is_reaonly_non_configurable ||
(FLAG_track_constant_fields && access_info.IsDataConstantField())) {
Node* value = jsgraph()->Constant(JSReceiver::GetDataProperty(&it));
if (!is_reaonly_non_configurable) {
// It's necessary to add dependency on the map that introduced
// the field.
DCHECK(access_info.IsDataConstantField());
DCHECK(!it.is_dictionary_holder());
Handle<Map> field_owner_map = it.GetFieldOwnerMap();
dependencies()->AssumeFieldOwner(field_owner_map);
}
return value;
}
}
}
return nullptr;
}
Node* PropertyAccessBuilder::BuildLoadDataField(
Handle<Name> name, PropertyAccessInfo const& access_info, Node* receiver,
Node** effect, Node** control) {
DCHECK(access_info.IsDataField() || access_info.IsDataConstantField());
receiver = ResolveHolder(access_info, receiver);
if (Node* value =
TryBuildLoadConstantDataField(name, access_info, receiver)) {
return value;
}
FieldIndex const field_index = access_info.field_index();
Type* const field_type = access_info.field_type();
MachineRepresentation const field_representation =
access_info.field_representation();
Node* storage = receiver;
if (!field_index.is_inobject()) {
storage = *effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectPropertiesOrHash()),
storage, *effect, *control);
}
FieldAccess field_access = {
kTaggedBase,
field_index.offset(),
name,
MaybeHandle<Map>(),
field_type,
MachineType::TypeForRepresentation(field_representation),
kFullWriteBarrier};
if (field_representation == MachineRepresentation::kFloat64) {
if (!field_index.is_inobject() || field_index.is_hidden_field() ||
!FLAG_unbox_double_fields) {
FieldAccess const storage_access = {kTaggedBase,
field_index.offset(),
name,
MaybeHandle<Map>(),
Type::OtherInternal(),
MachineType::TaggedPointer(),
kPointerWriteBarrier};
storage = *effect = graph()->NewNode(
simplified()->LoadField(storage_access), storage, *effect, *control);
field_access.offset = HeapNumber::kValueOffset;
field_access.name = MaybeHandle<Name>();
}
} else if (field_representation == MachineRepresentation::kTaggedPointer) {
// Remember the map of the field value, if its map is stable. This is
// used by the LoadElimination to eliminate map checks on the result.
Handle<Map> field_map;
if (access_info.field_map().ToHandle(&field_map)) {
if (field_map->is_stable()) {
dependencies()->AssumeMapStable(field_map);
field_access.map = field_map;
}
}
}
Node* value = *effect = graph()->NewNode(
simplified()->LoadField(field_access), storage, *effect, *control);
return value;
}
} // namespace compiler
} // namespace internal
} // namespace v8