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
16 namespace v8 {
17 namespace internal {
18 namespace compiler {
19
Reduce(Node * node)20 Reduction 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
ReduceParameter(Node * node)36 Reduction 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
SimplifyJSLoadContext(Node * node,Node * new_context,size_t new_depth)50 Reduction 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
SimplifyJSStoreContext(Node * node,Node * new_context,size_t new_depth)69 Reduction 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
88 namespace {
89
IsContextParameter(Node * node)90 bool 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.
GetSpecializationContext(JSHeapBroker * broker,Node * node,size_t * distance,Maybe<OuterContext> maybe_outer)101 base::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
ReduceJSLoadContext(Node * node)136 Reduction 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
ReduceJSStoreContext(Node * node)198 Reduction 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
GetModuleContext(JSHeapBroker * broker,Node * node,Maybe<OuterContext> maybe_context)227 base::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
ReduceJSGetImportMeta(Node * node)272 Reduction 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
isolate() const295 Isolate* JSContextSpecialization::isolate() const {
296 return jsgraph()->isolate();
297 }
298
299 } // namespace compiler
300 } // namespace internal
301 } // namespace v8
302