1// Copyright 2014 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/js-context-specialization.h"
6
7#include "src/compiler/common-operator.h"
8#include "src/compiler/js-graph.h"
9#include "src/compiler/js-heap-broker.h"
10#include "src/compiler/js-operator.h"
11#include "src/compiler/linkage.h"
12#include "src/compiler/node-matchers.h"
13#include "src/compiler/node-properties.h"
14#include "src/objects/contexts-inl.h"
15
16namespace v8 {
17namespace internal {
18namespace compiler {
19
20Reduction JSContextSpecialization::Reduce(Node* node) {
21  switch (node->opcode()) {
22    case IrOpcode::kParameter:
23      return ReduceParameter(node);
24    case IrOpcode::kJSLoadContext:
25      return ReduceJSLoadContext(node);
26    case IrOpcode::kJSStoreContext:
27      return ReduceJSStoreContext(node);
28    case IrOpcode::kJSGetImportMeta:
29      return ReduceJSGetImportMeta(node);
30    default:
31      break;
32  }
33  return NoChange();
34}
35
36Reduction JSContextSpecialization::ReduceParameter(Node* node) {
37  DCHECK_EQ(IrOpcode::kParameter, node->opcode());
38  int const index = ParameterIndexOf(node->op());
39  if (index == Linkage::kJSCallClosureParamIndex) {
40    // Constant-fold the function parameter {node}.
41    Handle<JSFunction> function;
42    if (closure().ToHandle(&function)) {
43      Node* value = jsgraph()->Constant(MakeRef(broker_, function));
44      return Replace(value);
45    }
46  }
47  return NoChange();
48}
49
50Reduction JSContextSpecialization::SimplifyJSLoadContext(Node* node,
51                                                         Node* new_context,
52                                                         size_t new_depth) {
53  DCHECK_EQ(IrOpcode::kJSLoadContext, node->opcode());
54  const ContextAccess& access = ContextAccessOf(node->op());
55  DCHECK_LE(new_depth, access.depth());
56
57  if (new_depth == access.depth() &&
58      new_context == NodeProperties::GetContextInput(node)) {
59    return NoChange();
60  }
61
62  const Operator* op = jsgraph_->javascript()->LoadContext(
63      new_depth, access.index(), access.immutable());
64  NodeProperties::ReplaceContextInput(node, new_context);
65  NodeProperties::ChangeOp(node, op);
66  return Changed(node);
67}
68
69Reduction JSContextSpecialization::SimplifyJSStoreContext(Node* node,
70                                                          Node* new_context,
71                                                          size_t new_depth) {
72  DCHECK_EQ(IrOpcode::kJSStoreContext, node->opcode());
73  const ContextAccess& access = ContextAccessOf(node->op());
74  DCHECK_LE(new_depth, access.depth());
75
76  if (new_depth == access.depth() &&
77      new_context == NodeProperties::GetContextInput(node)) {
78    return NoChange();
79  }
80
81  const Operator* op =
82      jsgraph_->javascript()->StoreContext(new_depth, access.index());
83  NodeProperties::ReplaceContextInput(node, new_context);
84  NodeProperties::ChangeOp(node, op);
85  return Changed(node);
86}
87
88namespace {
89
90bool IsContextParameter(Node* node) {
91  DCHECK_EQ(IrOpcode::kParameter, node->opcode());
92  return ParameterIndexOf(node->op()) ==
93         StartNode{NodeProperties::GetValueInput(node, 0)}
94             .ContextParameterIndex_MaybeNonStandardLayout();
95}
96
97// Given a context {node} and the {distance} from that context to the target
98// context (which we want to read from or store to), try to return a
99// specialization context.  If successful, update {distance} to whatever
100// distance remains from the specialization context.
101base::Optional<ContextRef> GetSpecializationContext(
102    JSHeapBroker* broker, Node* node, size_t* distance,
103    Maybe<OuterContext> maybe_outer) {
104  switch (node->opcode()) {
105    case IrOpcode::kHeapConstant: {
106      // TODO(jgruber,chromium:1209798): Using kAssumeMemoryFence works around
107      // the fact that the graph stores handles (and not refs). The assumption
108      // is that any handle inserted into the graph is safe to read; but we
109      // don't preserve the reason why it is safe to read. Thus we must
110      // over-approximate here and assume the existence of a memory fence. In
111      // the future, we should consider having the graph store ObjectRefs or
112      // ObjectData pointer instead, which would make new ref construction here
113      // unnecessary.
114      HeapObjectRef object =
115          MakeRefAssumeMemoryFence(broker, HeapConstantOf(node->op()));
116      if (object.IsContext()) return object.AsContext();
117      break;
118    }
119    case IrOpcode::kParameter: {
120      OuterContext outer;
121      if (maybe_outer.To(&outer) && IsContextParameter(node) &&
122          *distance >= outer.distance) {
123        *distance -= outer.distance;
124        return MakeRef(broker, outer.context);
125      }
126      break;
127    }
128    default:
129      break;
130  }
131  return base::Optional<ContextRef>();
132}
133
134}  // anonymous namespace
135
136Reduction JSContextSpecialization::ReduceJSLoadContext(Node* node) {
137  DCHECK_EQ(IrOpcode::kJSLoadContext, node->opcode());
138
139  const ContextAccess& access = ContextAccessOf(node->op());
140  size_t depth = access.depth();
141
142  // First walk up the context chain in the graph as far as possible.
143  Node* context = NodeProperties::GetOuterContext(node, &depth);
144
145  base::Optional<ContextRef> maybe_concrete =
146      GetSpecializationContext(broker(), context, &depth, outer());
147  if (!maybe_concrete.has_value()) {
148    // We do not have a concrete context object, so we can only partially reduce
149    // the load by folding-in the outer context node.
150    return SimplifyJSLoadContext(node, context, depth);
151  }
152
153  // Now walk up the concrete context chain for the remaining depth.
154  ContextRef concrete = maybe_concrete.value();
155  concrete = concrete.previous(&depth);
156  if (depth > 0) {
157    TRACE_BROKER_MISSING(broker(), "previous value for context " << concrete);
158    return SimplifyJSLoadContext(node, jsgraph()->Constant(concrete), depth);
159  }
160
161  if (!access.immutable()) {
162    // We found the requested context object but since the context slot is
163    // mutable we can only partially reduce the load.
164    return SimplifyJSLoadContext(node, jsgraph()->Constant(concrete), depth);
165  }
166
167  // This will hold the final value, if we can figure it out.
168  base::Optional<ObjectRef> maybe_value;
169  maybe_value = concrete.get(static_cast<int>(access.index()));
170
171  if (!maybe_value.has_value()) {
172    TRACE_BROKER_MISSING(broker(), "slot value " << access.index()
173                                                 << " for context "
174                                                 << concrete);
175    return SimplifyJSLoadContext(node, jsgraph()->Constant(concrete), depth);
176  }
177
178  if (!maybe_value->IsSmi()) {
179    // Even though the context slot is immutable, the context might have escaped
180    // before the function to which it belongs has initialized the slot.
181    // We must be conservative and check if the value in the slot is currently
182    // the hole or undefined. Only if it is neither of these, can we be sure
183    // that it won't change anymore.
184    OddballType oddball_type = maybe_value->AsHeapObject().map().oddball_type();
185    if (oddball_type == OddballType::kUndefined ||
186        oddball_type == OddballType::kHole) {
187      return SimplifyJSLoadContext(node, jsgraph()->Constant(concrete), depth);
188    }
189  }
190
191  // Success. The context load can be replaced with the constant.
192  Node* constant = jsgraph_->Constant(*maybe_value);
193  ReplaceWithValue(node, constant);
194  return Replace(constant);
195}
196
197
198Reduction JSContextSpecialization::ReduceJSStoreContext(Node* node) {
199  DCHECK_EQ(IrOpcode::kJSStoreContext, node->opcode());
200
201  const ContextAccess& access = ContextAccessOf(node->op());
202  size_t depth = access.depth();
203
204  // First walk up the context chain in the graph until we reduce the depth to 0
205  // or hit a node that does not have a CreateXYZContext operator.
206  Node* context = NodeProperties::GetOuterContext(node, &depth);
207
208  base::Optional<ContextRef> maybe_concrete =
209      GetSpecializationContext(broker(), context, &depth, outer());
210  if (!maybe_concrete.has_value()) {
211    // We do not have a concrete context object, so we can only partially reduce
212    // the load by folding-in the outer context node.
213    return SimplifyJSStoreContext(node, context, depth);
214  }
215
216  // Now walk up the concrete context chain for the remaining depth.
217  ContextRef concrete = maybe_concrete.value();
218  concrete = concrete.previous(&depth);
219  if (depth > 0) {
220    TRACE_BROKER_MISSING(broker(), "previous value for context " << concrete);
221    return SimplifyJSStoreContext(node, jsgraph()->Constant(concrete), depth);
222  }
223
224  return SimplifyJSStoreContext(node, jsgraph()->Constant(concrete), depth);
225}
226
227base::Optional<ContextRef> GetModuleContext(JSHeapBroker* broker, Node* node,
228                                            Maybe<OuterContext> maybe_context) {
229  size_t depth = std::numeric_limits<size_t>::max();
230  Node* context = NodeProperties::GetOuterContext(node, &depth);
231
232  auto find_context = [](ContextRef c) {
233    while (c.map().instance_type() != MODULE_CONTEXT_TYPE) {
234      size_t depth = 1;
235      c = c.previous(&depth);
236      CHECK_EQ(depth, 0);
237    }
238    return c;
239  };
240
241  switch (context->opcode()) {
242    case IrOpcode::kHeapConstant: {
243      // TODO(jgruber,chromium:1209798): Using kAssumeMemoryFence works around
244      // the fact that the graph stores handles (and not refs). The assumption
245      // is that any handle inserted into the graph is safe to read; but we
246      // don't preserve the reason why it is safe to read. Thus we must
247      // over-approximate here and assume the existence of a memory fence. In
248      // the future, we should consider having the graph store ObjectRefs or
249      // ObjectData pointer instead, which would make new ref construction here
250      // unnecessary.
251      HeapObjectRef object =
252          MakeRefAssumeMemoryFence(broker, HeapConstantOf(context->op()));
253      if (object.IsContext()) {
254        return find_context(object.AsContext());
255      }
256      break;
257    }
258    case IrOpcode::kParameter: {
259      OuterContext outer;
260      if (maybe_context.To(&outer) && IsContextParameter(context)) {
261        return find_context(MakeRef(broker, outer.context));
262      }
263      break;
264    }
265    default:
266      break;
267  }
268
269  return base::Optional<ContextRef>();
270}
271
272Reduction JSContextSpecialization::ReduceJSGetImportMeta(Node* node) {
273  base::Optional<ContextRef> maybe_context =
274      GetModuleContext(broker(), node, outer());
275  if (!maybe_context.has_value()) return NoChange();
276
277  ContextRef context = maybe_context.value();
278  base::Optional<ObjectRef> module = context.get(Context::EXTENSION_INDEX);
279  if (!module.has_value()) return NoChange();
280  base::Optional<ObjectRef> import_meta =
281      module->AsSourceTextModule().import_meta();
282  if (!import_meta.has_value()) return NoChange();
283  if (!import_meta->IsJSObject()) {
284    DCHECK(import_meta->IsTheHole());
285    // The import.meta object has not yet been created. Let JSGenericLowering
286    // replace the operator with a runtime call.
287    return NoChange();
288  }
289
290  Node* import_meta_const = jsgraph()->Constant(*import_meta);
291  ReplaceWithValue(node, import_meta_const);
292  return Changed(import_meta_const);
293}
294
295Isolate* JSContextSpecialization::isolate() const {
296  return jsgraph()->isolate();
297}
298
299}  // namespace compiler
300}  // namespace internal
301}  // namespace v8
302