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(JSFunctionRef(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 Node* const start = NodeProperties::GetValueInput(node, 0);
93 DCHECK_EQ(IrOpcode::kStart, start->opcode());
94 int const index = ParameterIndexOf(node->op());
95 // The context is always the last parameter to a JavaScript function, and
96 // {Parameter} indices start at -1, so value outputs of {Start} look like
97 // this: closure, receiver, param0, ..., paramN, context.
98 return index == start->op()->ValueOutputCount() - 2;
99 }
100
101 // Given a context {node} and the {distance} from that context to the target
102 // context (which we want to read from or store to), try to return a
103 // specialization context. If successful, update {distance} to whatever
104 // distance remains from the specialization context.
GetSpecializationContext(JSHeapBroker * broker,Node * node,size_t * distance,Maybe<OuterContext> maybe_outer)105 base::Optional<ContextRef> GetSpecializationContext(
106 JSHeapBroker* broker, Node* node, size_t* distance,
107 Maybe<OuterContext> maybe_outer) {
108 switch (node->opcode()) {
109 case IrOpcode::kHeapConstant: {
110 HeapObjectRef object(broker, HeapConstantOf(node->op()));
111 if (object.IsContext()) return object.AsContext();
112 break;
113 }
114 case IrOpcode::kParameter: {
115 OuterContext outer;
116 if (maybe_outer.To(&outer) && IsContextParameter(node) &&
117 *distance >= outer.distance) {
118 *distance -= outer.distance;
119 return ContextRef(broker, outer.context);
120 }
121 break;
122 }
123 default:
124 break;
125 }
126 return base::Optional<ContextRef>();
127 }
128
129 } // anonymous namespace
130
ReduceJSLoadContext(Node * node)131 Reduction JSContextSpecialization::ReduceJSLoadContext(Node* node) {
132 DCHECK_EQ(IrOpcode::kJSLoadContext, node->opcode());
133
134 const ContextAccess& access = ContextAccessOf(node->op());
135 size_t depth = access.depth();
136
137 // First walk up the context chain in the graph as far as possible.
138 Node* context = NodeProperties::GetOuterContext(node, &depth);
139
140 base::Optional<ContextRef> maybe_concrete =
141 GetSpecializationContext(broker(), context, &depth, outer());
142 if (!maybe_concrete.has_value()) {
143 // We do not have a concrete context object, so we can only partially reduce
144 // the load by folding-in the outer context node.
145 return SimplifyJSLoadContext(node, context, depth);
146 }
147
148 // Now walk up the concrete context chain for the remaining depth.
149 ContextRef concrete = maybe_concrete.value();
150 concrete = concrete.previous(&depth);
151 if (depth > 0) {
152 TRACE_BROKER_MISSING(broker(), "previous value for context " << concrete);
153 return SimplifyJSLoadContext(node, jsgraph()->Constant(concrete), depth);
154 }
155
156 if (!access.immutable()) {
157 // We found the requested context object but since the context slot is
158 // mutable we can only partially reduce the load.
159 return SimplifyJSLoadContext(node, jsgraph()->Constant(concrete), depth);
160 }
161
162 // This will hold the final value, if we can figure it out.
163 base::Optional<ObjectRef> maybe_value;
164 maybe_value = concrete.get(static_cast<int>(access.index()));
165
166 if (!maybe_value.has_value()) {
167 TRACE_BROKER_MISSING(broker(), "slot value " << access.index()
168 << " for context "
169 << concrete);
170 return SimplifyJSLoadContext(node, jsgraph()->Constant(concrete), depth);
171 }
172
173 if (!maybe_value->IsSmi()) {
174 // Even though the context slot is immutable, the context might have escaped
175 // before the function to which it belongs has initialized the slot.
176 // We must be conservative and check if the value in the slot is currently
177 // the hole or undefined. Only if it is neither of these, can we be sure
178 // that it won't change anymore.
179 OddballType oddball_type = maybe_value->AsHeapObject().map().oddball_type();
180 if (oddball_type == OddballType::kUndefined ||
181 oddball_type == OddballType::kHole) {
182 return SimplifyJSLoadContext(node, jsgraph()->Constant(concrete), depth);
183 }
184 }
185
186 // Success. The context load can be replaced with the constant.
187 Node* constant = jsgraph_->Constant(*maybe_value);
188 ReplaceWithValue(node, constant);
189 return Replace(constant);
190 }
191
192
ReduceJSStoreContext(Node * node)193 Reduction JSContextSpecialization::ReduceJSStoreContext(Node* node) {
194 DCHECK_EQ(IrOpcode::kJSStoreContext, node->opcode());
195
196 const ContextAccess& access = ContextAccessOf(node->op());
197 size_t depth = access.depth();
198
199 // First walk up the context chain in the graph until we reduce the depth to 0
200 // or hit a node that does not have a CreateXYZContext operator.
201 Node* context = NodeProperties::GetOuterContext(node, &depth);
202
203 base::Optional<ContextRef> maybe_concrete =
204 GetSpecializationContext(broker(), context, &depth, outer());
205 if (!maybe_concrete.has_value()) {
206 // We do not have a concrete context object, so we can only partially reduce
207 // the load by folding-in the outer context node.
208 return SimplifyJSStoreContext(node, context, depth);
209 }
210
211 // Now walk up the concrete context chain for the remaining depth.
212 ContextRef concrete = maybe_concrete.value();
213 concrete = concrete.previous(&depth);
214 if (depth > 0) {
215 TRACE_BROKER_MISSING(broker(), "previous value for context " << concrete);
216 return SimplifyJSStoreContext(node, jsgraph()->Constant(concrete), depth);
217 }
218
219 return SimplifyJSStoreContext(node, jsgraph()->Constant(concrete), depth);
220 }
221
GetModuleContext(JSHeapBroker * broker,Node * node,Maybe<OuterContext> maybe_context)222 base::Optional<ContextRef> GetModuleContext(JSHeapBroker* broker, Node* node,
223 Maybe<OuterContext> maybe_context) {
224 size_t depth = std::numeric_limits<size_t>::max();
225 Node* context = NodeProperties::GetOuterContext(node, &depth);
226
227 auto find_context = [](ContextRef c) {
228 while (c.map().instance_type() != MODULE_CONTEXT_TYPE) {
229 size_t depth = 1;
230 c = c.previous(&depth);
231 CHECK_EQ(depth, 0);
232 }
233 return c;
234 };
235
236 switch (context->opcode()) {
237 case IrOpcode::kHeapConstant: {
238 HeapObjectRef object(broker, HeapConstantOf(context->op()));
239 if (object.IsContext()) {
240 return find_context(object.AsContext());
241 }
242 break;
243 }
244 case IrOpcode::kParameter: {
245 OuterContext outer;
246 if (maybe_context.To(&outer) && IsContextParameter(context)) {
247 return find_context(ContextRef(broker, outer.context));
248 }
249 break;
250 }
251 default:
252 break;
253 }
254
255 return base::Optional<ContextRef>();
256 }
257
ReduceJSGetImportMeta(Node * node)258 Reduction JSContextSpecialization::ReduceJSGetImportMeta(Node* node) {
259 base::Optional<ContextRef> maybe_context =
260 GetModuleContext(broker(), node, outer());
261 if (!maybe_context.has_value()) return NoChange();
262
263 ContextRef context = maybe_context.value();
264 SourceTextModuleRef module =
265 context.get(Context::EXTENSION_INDEX).value().AsSourceTextModule();
266 ObjectRef import_meta = module.import_meta();
267 if (import_meta.IsJSObject()) {
268 Node* import_meta_const = jsgraph()->Constant(import_meta);
269 ReplaceWithValue(node, import_meta_const);
270 return Changed(import_meta_const);
271 } else {
272 DCHECK(import_meta.IsTheHole());
273 // The import.meta object has not yet been created. Let JSGenericLowering
274 // replace the operator with a runtime call.
275 return NoChange();
276 }
277 }
278
isolate() const279 Isolate* JSContextSpecialization::isolate() const {
280 return jsgraph()->isolate();
281 }
282
283 } // namespace compiler
284 } // namespace internal
285 } // namespace v8
286