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-operator.h"
10 #include "src/compiler/linkage.h"
11 #include "src/compiler/node-matchers.h"
12 #include "src/compiler/node-properties.h"
13 #include "src/contexts-inl.h"
14 
15 namespace v8 {
16 namespace internal {
17 namespace compiler {
18 
Reduce(Node * node)19 Reduction JSContextSpecialization::Reduce(Node* node) {
20   switch (node->opcode()) {
21     case IrOpcode::kParameter:
22       return ReduceParameter(node);
23     case IrOpcode::kJSLoadContext:
24       return ReduceJSLoadContext(node);
25     case IrOpcode::kJSStoreContext:
26       return ReduceJSStoreContext(node);
27     default:
28       break;
29   }
30   return NoChange();
31 }
32 
ReduceParameter(Node * node)33 Reduction JSContextSpecialization::ReduceParameter(Node* node) {
34   DCHECK_EQ(IrOpcode::kParameter, node->opcode());
35   int const index = ParameterIndexOf(node->op());
36   if (index == Linkage::kJSCallClosureParamIndex) {
37     // Constant-fold the function parameter {node}.
38     Handle<JSFunction> function;
39     if (closure().ToHandle(&function)) {
40       Node* value = jsgraph()->HeapConstant(function);
41       return Replace(value);
42     }
43   }
44   return NoChange();
45 }
46 
SimplifyJSLoadContext(Node * node,Node * new_context,size_t new_depth)47 Reduction JSContextSpecialization::SimplifyJSLoadContext(Node* node,
48                                                          Node* new_context,
49                                                          size_t new_depth) {
50   DCHECK_EQ(IrOpcode::kJSLoadContext, node->opcode());
51   const ContextAccess& access = ContextAccessOf(node->op());
52   DCHECK_LE(new_depth, access.depth());
53 
54   if (new_depth == access.depth() &&
55       new_context == NodeProperties::GetContextInput(node)) {
56     return NoChange();
57   }
58 
59   const Operator* op = jsgraph_->javascript()->LoadContext(
60       new_depth, access.index(), access.immutable());
61   NodeProperties::ReplaceContextInput(node, new_context);
62   NodeProperties::ChangeOp(node, op);
63   return Changed(node);
64 }
65 
SimplifyJSStoreContext(Node * node,Node * new_context,size_t new_depth)66 Reduction JSContextSpecialization::SimplifyJSStoreContext(Node* node,
67                                                           Node* new_context,
68                                                           size_t new_depth) {
69   DCHECK_EQ(IrOpcode::kJSStoreContext, node->opcode());
70   const ContextAccess& access = ContextAccessOf(node->op());
71   DCHECK_LE(new_depth, access.depth());
72 
73   if (new_depth == access.depth() &&
74       new_context == NodeProperties::GetContextInput(node)) {
75     return NoChange();
76   }
77 
78   const Operator* op =
79       jsgraph_->javascript()->StoreContext(new_depth, access.index());
80   NodeProperties::ReplaceContextInput(node, new_context);
81   NodeProperties::ChangeOp(node, op);
82   return Changed(node);
83 }
84 
85 namespace {
86 
IsContextParameter(Node * node)87 bool IsContextParameter(Node* node) {
88   DCHECK_EQ(IrOpcode::kParameter, node->opcode());
89   Node* const start = NodeProperties::GetValueInput(node, 0);
90   DCHECK_EQ(IrOpcode::kStart, start->opcode());
91   int const index = ParameterIndexOf(node->op());
92   // The context is always the last parameter to a JavaScript function, and
93   // {Parameter} indices start at -1, so value outputs of {Start} look like
94   // this: closure, receiver, param0, ..., paramN, context.
95   return index == start->op()->ValueOutputCount() - 2;
96 }
97 
98 // Given a context {node} and the {distance} from that context to the target
99 // context (which we want to read from or store to), try to return a
100 // specialization context.  If successful, update {distance} to whatever
101 // distance remains from the specialization context.
GetSpecializationContext(JSHeapBroker * broker,Node * node,size_t * distance,Maybe<OuterContext> maybe_outer)102 base::Optional<ContextRef> GetSpecializationContext(
103     JSHeapBroker* broker, Node* node, size_t* distance,
104     Maybe<OuterContext> maybe_outer) {
105   switch (node->opcode()) {
106     case IrOpcode::kHeapConstant: {
107       HeapObjectRef object(broker, HeapConstantOf(node->op()));
108       if (object.IsContext()) return object.AsContext();
109       break;
110     }
111     case IrOpcode::kParameter: {
112       OuterContext outer;
113       if (maybe_outer.To(&outer) && IsContextParameter(node) &&
114           *distance >= outer.distance) {
115         *distance -= outer.distance;
116         return ContextRef(broker, outer.context);
117       }
118       break;
119     }
120     default:
121       break;
122   }
123   return base::Optional<ContextRef>();
124 }
125 
126 }  // anonymous namespace
127 
ReduceJSLoadContext(Node * node)128 Reduction JSContextSpecialization::ReduceJSLoadContext(Node* node) {
129   DCHECK_EQ(IrOpcode::kJSLoadContext, node->opcode());
130 
131   const ContextAccess& access = ContextAccessOf(node->op());
132   size_t depth = access.depth();
133 
134   // First walk up the context chain in the graph as far as possible.
135   Node* context = NodeProperties::GetOuterContext(node, &depth);
136 
137   base::Optional<ContextRef> maybe_concrete =
138       GetSpecializationContext(js_heap_broker(), context, &depth, outer());
139   if (!maybe_concrete.has_value()) {
140     // We do not have a concrete context object, so we can only partially reduce
141     // the load by folding-in the outer context node.
142     return SimplifyJSLoadContext(node, context, depth);
143   }
144 
145   // Now walk up the concrete context chain for the remaining depth.
146   ContextRef concrete = maybe_concrete.value();
147   for (; depth > 0; --depth) {
148     concrete = concrete.previous().value();
149   }
150 
151   if (!access.immutable()) {
152     // We found the requested context object but since the context slot is
153     // mutable we can only partially reduce the load.
154     return SimplifyJSLoadContext(node, jsgraph()->Constant(concrete), depth);
155   }
156 
157   // This will hold the final value, if we can figure it out.
158   base::Optional<ObjectRef> maybe_value;
159 
160   maybe_value = concrete.get(static_cast<int>(access.index()));
161   if (maybe_value.has_value() && !maybe_value->IsSmi()) {
162     // Even though the context slot is immutable, the context might have escaped
163     // before the function to which it belongs has initialized the slot.
164     // We must be conservative and check if the value in the slot is currently
165     // the hole or undefined. Only if it is neither of these, can we be sure
166     // that it won't change anymore.
167     OddballType oddball_type = maybe_value->oddball_type();
168     if (oddball_type == OddballType::kUndefined ||
169         oddball_type == OddballType::kHole) {
170       maybe_value.reset();
171     }
172   }
173 
174   if (!maybe_value.has_value()) {
175     return SimplifyJSLoadContext(node, jsgraph()->Constant(concrete), depth);
176   }
177 
178   // Success. The context load can be replaced with the constant.
179   // TODO(titzer): record the specialization for sharing code across
180   // multiple contexts that have the same value in the corresponding context
181   // slot.
182   Node* constant = jsgraph_->Constant(*maybe_value);
183   ReplaceWithValue(node, constant);
184   return Replace(constant);
185 }
186 
187 
ReduceJSStoreContext(Node * node)188 Reduction JSContextSpecialization::ReduceJSStoreContext(Node* node) {
189   DCHECK_EQ(IrOpcode::kJSStoreContext, node->opcode());
190 
191   const ContextAccess& access = ContextAccessOf(node->op());
192   size_t depth = access.depth();
193 
194   // First walk up the context chain in the graph until we reduce the depth to 0
195   // or hit a node that does not have a CreateXYZContext operator.
196   Node* context = NodeProperties::GetOuterContext(node, &depth);
197 
198   base::Optional<ContextRef> maybe_concrete =
199       GetSpecializationContext(js_heap_broker(), context, &depth, outer());
200   if (!maybe_concrete.has_value()) {
201     // We do not have a concrete context object, so we can only partially reduce
202     // the load by folding-in the outer context node.
203     return SimplifyJSStoreContext(node, context, depth);
204   }
205 
206   // Now walk up the concrete context chain for the remaining depth.
207   ContextRef concrete = maybe_concrete.value();
208   for (; depth > 0; --depth) {
209     concrete = concrete.previous().value();
210   }
211 
212   return SimplifyJSStoreContext(node, jsgraph()->Constant(concrete), depth);
213 }
214 
215 
isolate() const216 Isolate* JSContextSpecialization::isolate() const {
217   return jsgraph()->isolate();
218 }
219 
220 }  // namespace compiler
221 }  // namespace internal
222 }  // namespace v8
223