1// Copyright 2017 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "src/compiler/property-access-builder.h"
6
7#include "src/base/optional.h"
8#include "src/compiler/access-builder.h"
9#include "src/compiler/access-info.h"
10#include "src/compiler/compilation-dependencies.h"
11#include "src/compiler/js-graph.h"
12#include "src/compiler/node-matchers.h"
13#include "src/compiler/simplified-operator.h"
14#include "src/execution/isolate-inl.h"
15#include "src/objects/field-index-inl.h"
16#include "src/objects/heap-number.h"
17#include "src/objects/internal-index.h"
18#include "src/objects/lookup.h"
19#include "src/objects/property-details.h"
20
21namespace v8 {
22namespace internal {
23namespace compiler {
24
25Graph* PropertyAccessBuilder::graph() const { return jsgraph()->graph(); }
26
27Isolate* PropertyAccessBuilder::isolate() const { return jsgraph()->isolate(); }
28
29CommonOperatorBuilder* PropertyAccessBuilder::common() const {
30  return jsgraph()->common();
31}
32
33SimplifiedOperatorBuilder* PropertyAccessBuilder::simplified() const {
34  return jsgraph()->simplified();
35}
36
37bool HasOnlyStringMaps(JSHeapBroker* broker, ZoneVector<MapRef> const& maps) {
38  for (MapRef map : maps) {
39    if (!map.IsStringMap()) return false;
40  }
41  return true;
42}
43
44namespace {
45
46bool HasOnlyNumberMaps(JSHeapBroker* broker, ZoneVector<MapRef> const& maps) {
47  for (MapRef map : maps) {
48    if (map.instance_type() != HEAP_NUMBER_TYPE) return false;
49  }
50  return true;
51}
52
53}  // namespace
54
55bool PropertyAccessBuilder::TryBuildStringCheck(JSHeapBroker* broker,
56                                                ZoneVector<MapRef> const& maps,
57                                                Node** receiver, Effect* effect,
58                                                Control control) {
59  if (HasOnlyStringMaps(broker, maps)) {
60    // Monormorphic string access (ignoring the fact that there are multiple
61    // String maps).
62    *receiver = *effect =
63        graph()->NewNode(simplified()->CheckString(FeedbackSource()), *receiver,
64                         *effect, control);
65    return true;
66  }
67  return false;
68}
69
70bool PropertyAccessBuilder::TryBuildNumberCheck(JSHeapBroker* broker,
71                                                ZoneVector<MapRef> const& maps,
72                                                Node** receiver, Effect* effect,
73                                                Control control) {
74  if (HasOnlyNumberMaps(broker, maps)) {
75    // Monomorphic number access (we also deal with Smis here).
76    *receiver = *effect =
77        graph()->NewNode(simplified()->CheckNumber(FeedbackSource()), *receiver,
78                         *effect, control);
79    return true;
80  }
81  return false;
82}
83
84void PropertyAccessBuilder::BuildCheckMaps(Node* object, Effect* effect,
85                                           Control control,
86                                           ZoneVector<MapRef> const& maps) {
87  HeapObjectMatcher m(object);
88  if (m.HasResolvedValue()) {
89    MapRef object_map = m.Ref(broker()).map();
90    if (object_map.is_stable()) {
91      for (MapRef map : maps) {
92        if (map.equals(object_map)) {
93          dependencies()->DependOnStableMap(object_map);
94          return;
95        }
96      }
97    }
98  }
99  ZoneHandleSet<Map> map_set;
100  CheckMapsFlags flags = CheckMapsFlag::kNone;
101  for (MapRef map : maps) {
102    map_set.insert(map.object(), graph()->zone());
103    if (map.is_migration_target()) {
104      flags |= CheckMapsFlag::kTryMigrateInstance;
105    }
106  }
107  *effect = graph()->NewNode(simplified()->CheckMaps(flags, map_set), object,
108                             *effect, control);
109}
110
111Node* PropertyAccessBuilder::BuildCheckValue(Node* receiver, Effect* effect,
112                                             Control control,
113                                             Handle<HeapObject> value) {
114  HeapObjectMatcher m(receiver);
115  if (m.Is(value)) return receiver;
116  Node* expected = jsgraph()->HeapConstant(value);
117  Node* check =
118      graph()->NewNode(simplified()->ReferenceEqual(), receiver, expected);
119  *effect =
120      graph()->NewNode(simplified()->CheckIf(DeoptimizeReason::kWrongValue),
121                       check, *effect, control);
122  return expected;
123}
124
125Node* PropertyAccessBuilder::ResolveHolder(
126    PropertyAccessInfo const& access_info, Node* lookup_start_object) {
127  base::Optional<JSObjectRef> holder = access_info.holder();
128  if (holder.has_value()) {
129    return jsgraph()->Constant(holder.value());
130  }
131  return lookup_start_object;
132}
133
134MachineRepresentation PropertyAccessBuilder::ConvertRepresentation(
135    Representation representation) {
136  switch (representation.kind()) {
137    case Representation::kSmi:
138      return MachineRepresentation::kTaggedSigned;
139    case Representation::kDouble:
140      return MachineRepresentation::kFloat64;
141    case Representation::kHeapObject:
142      return MachineRepresentation::kTaggedPointer;
143    case Representation::kTagged:
144      return MachineRepresentation::kTagged;
145    default:
146      UNREACHABLE();
147  }
148}
149
150base::Optional<Node*> PropertyAccessBuilder::FoldLoadDictPrototypeConstant(
151    PropertyAccessInfo const& access_info) {
152  DCHECK(V8_DICT_PROPERTY_CONST_TRACKING_BOOL);
153  DCHECK(access_info.IsDictionaryProtoDataConstant());
154
155  InternalIndex index = access_info.dictionary_index();
156  base::Optional<ObjectRef> value =
157      access_info.holder()->GetOwnDictionaryProperty(index, dependencies());
158  if (!value) return {};
159
160  for (MapRef map : access_info.lookup_start_object_maps()) {
161    Handle<Map> map_handle = map.object();
162    // Non-JSReceivers that passed AccessInfoFactory::ComputePropertyAccessInfo
163    // must have different lookup start map.
164    if (!map_handle->IsJSReceiverMap()) {
165      // Perform the implicit ToObject for primitives here.
166      // Implemented according to ES6 section 7.3.2 GetV (V, P).
167      JSFunction constructor =
168          Map::GetConstructorFunction(
169              *map_handle, *broker()->target_native_context().object())
170              .value();
171      // {constructor.initial_map()} is loaded/stored with acquire-release
172      // semantics for constructors.
173      map = MakeRefAssumeMemoryFence(broker(), constructor.initial_map());
174      DCHECK(map.object()->IsJSObjectMap());
175    }
176    dependencies()->DependOnConstantInDictionaryPrototypeChain(
177        map, access_info.name(), value.value(), PropertyKind::kData);
178  }
179
180  return jsgraph()->Constant(value.value());
181}
182
183Node* PropertyAccessBuilder::TryFoldLoadConstantDataField(
184    NameRef const& name, PropertyAccessInfo const& access_info,
185    Node* lookup_start_object) {
186  if (!access_info.IsFastDataConstant()) return nullptr;
187
188  // First, determine if we have a constant holder to load from.
189  base::Optional<JSObjectRef> holder = access_info.holder();
190
191  // If {access_info} has a holder, just use it.
192  if (!holder.has_value()) {
193    // Otherwise, try to match the {lookup_start_object} as a constant.
194    HeapObjectMatcher m(lookup_start_object);
195    if (!m.HasResolvedValue() || !m.Ref(broker()).IsJSObject()) return nullptr;
196
197    // Let us make sure the actual map of the constant lookup_start_object is
198    // among the maps in {access_info}.
199    MapRef lookup_start_object_map = m.Ref(broker()).map();
200    if (std::find_if(access_info.lookup_start_object_maps().begin(),
201                     access_info.lookup_start_object_maps().end(),
202                     [&](MapRef map) {
203                       return map.equals(lookup_start_object_map);
204                     }) == access_info.lookup_start_object_maps().end()) {
205      // The map of the lookup_start_object is not in the feedback, let us bail
206      // out.
207      return nullptr;
208    }
209    holder = m.Ref(broker()).AsJSObject();
210  }
211
212  base::Optional<ObjectRef> value =
213      holder->GetOwnFastDataProperty(access_info.field_representation(),
214                                     access_info.field_index(), dependencies());
215  return value.has_value() ? jsgraph()->Constant(*value) : nullptr;
216}
217
218Node* PropertyAccessBuilder::BuildLoadDataField(NameRef const& name,
219                                                Node* holder,
220                                                FieldAccess& field_access,
221                                                bool is_inobject, Node** effect,
222                                                Node** control) {
223  Node* storage = holder;
224  if (!is_inobject) {
225    storage = *effect = graph()->NewNode(
226        simplified()->LoadField(
227            AccessBuilder::ForJSObjectPropertiesOrHashKnownPointer()),
228        storage, *effect, *control);
229  }
230  if (field_access.machine_type.representation() ==
231      MachineRepresentation::kFloat64) {
232    if (dependencies() == nullptr) {
233      FieldAccess const storage_access = {kTaggedBase,
234                                          field_access.offset,
235                                          name.object(),
236                                          MaybeHandle<Map>(),
237                                          Type::Any(),
238                                          MachineType::AnyTagged(),
239                                          kPointerWriteBarrier,
240                                          field_access.const_field_info};
241      storage = *effect = graph()->NewNode(
242          simplified()->LoadField(storage_access), storage, *effect, *control);
243      // We expect the loaded value to be a heap number here. With
244      // in-place field representation changes it is possible this is a
245      // no longer a heap number without map transitions. If we haven't taken
246      // a dependency on field representation, we should verify the loaded
247      // value is a heap number.
248      storage = *effect = graph()->NewNode(simplified()->CheckHeapObject(),
249                                           storage, *effect, *control);
250      Node* map = *effect =
251          graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
252                           storage, *effect, *control);
253      Node* is_heap_number =
254          graph()->NewNode(simplified()->ReferenceEqual(), map,
255                           jsgraph()->HeapNumberMapConstant());
256      *effect = graph()->NewNode(
257          simplified()->CheckIf(DeoptimizeReason::kNotAHeapNumber),
258          is_heap_number, *effect, *control);
259    } else {
260      FieldAccess const storage_access = {kTaggedBase,
261                                          field_access.offset,
262                                          name.object(),
263                                          MaybeHandle<Map>(),
264                                          Type::OtherInternal(),
265                                          MachineType::TaggedPointer(),
266                                          kPointerWriteBarrier,
267                                          field_access.const_field_info};
268      storage = *effect = graph()->NewNode(
269          simplified()->LoadField(storage_access), storage, *effect, *control);
270    }
271    field_access.offset = HeapNumber::kValueOffset;
272    field_access.name = MaybeHandle<Name>();
273  }
274  Node* value = *effect = graph()->NewNode(
275      simplified()->LoadField(field_access), storage, *effect, *control);
276  return value;
277}
278
279Node* PropertyAccessBuilder::BuildLoadDataField(
280    NameRef const& name, PropertyAccessInfo const& access_info,
281    Node* lookup_start_object, Node** effect, Node** control) {
282  DCHECK(access_info.IsDataField() || access_info.IsFastDataConstant());
283
284  if (Node* value = TryFoldLoadConstantDataField(name, access_info,
285                                                 lookup_start_object)) {
286    return value;
287  }
288
289  MachineRepresentation const field_representation =
290      ConvertRepresentation(access_info.field_representation());
291  Node* storage = ResolveHolder(access_info, lookup_start_object);
292
293  FieldAccess field_access = {
294      kTaggedBase,
295      access_info.field_index().offset(),
296      name.object(),
297      MaybeHandle<Map>(),
298      access_info.field_type(),
299      MachineType::TypeForRepresentation(field_representation),
300      kFullWriteBarrier,
301      access_info.GetConstFieldInfo()};
302  if (field_representation == MachineRepresentation::kTaggedPointer ||
303      field_representation == MachineRepresentation::kCompressedPointer) {
304    // Remember the map of the field value, if its map is stable. This is
305    // used by the LoadElimination to eliminate map checks on the result.
306    base::Optional<MapRef> field_map = access_info.field_map();
307    if (field_map.has_value()) {
308      if (field_map->is_stable()) {
309        dependencies()->DependOnStableMap(field_map.value());
310        field_access.map = field_map->object();
311      }
312    }
313  }
314  return BuildLoadDataField(name, storage, field_access,
315                            access_info.field_index().is_inobject(), effect,
316                            control);
317}
318
319}  // namespace compiler
320}  // namespace internal
321}  // namespace v8
322