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