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 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 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 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 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 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. 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 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 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 216 Isolate* JSContextSpecialization::isolate() const { 217 return jsgraph()->isolate(); 218 } 219 220 } // namespace compiler 221 } // namespace internal 222 } // namespace v8 223