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 #include "src/compiler/js-graph.h" 7 #include "src/compiler/js-operator.h" 8 #include "src/compiler/node-matchers.h" 9 #include "src/compiler/node-properties.h" 10 #include "src/compiler/source-position.h" 11 #include "test/cctest/cctest.h" 12 #include "test/cctest/compiler/function-tester.h" 13 #include "test/cctest/compiler/graph-builder-tester.h" 14 15 namespace v8 { 16 namespace internal { 17 namespace compiler { 18 19 class ContextSpecializationTester : public HandleAndZoneScope { 20 public: 21 ContextSpecializationTester() 22 : graph_(new (main_zone()) Graph(main_zone())), 23 common_(main_zone()), 24 javascript_(main_zone()), 25 machine_(main_zone()), 26 simplified_(main_zone()), 27 jsgraph_(main_isolate(), graph(), common(), &javascript_, &simplified_, 28 &machine_), 29 reducer_(main_zone(), graph()), 30 spec_(&reducer_, jsgraph(), MaybeHandle<Context>()) {} 31 32 JSContextSpecialization* spec() { return &spec_; } 33 Factory* factory() { return main_isolate()->factory(); } 34 CommonOperatorBuilder* common() { return &common_; } 35 JSOperatorBuilder* javascript() { return &javascript_; } 36 SimplifiedOperatorBuilder* simplified() { return &simplified_; } 37 JSGraph* jsgraph() { return &jsgraph_; } 38 Graph* graph() { return graph_; } 39 40 private: 41 Graph* graph_; 42 CommonOperatorBuilder common_; 43 JSOperatorBuilder javascript_; 44 MachineOperatorBuilder machine_; 45 SimplifiedOperatorBuilder simplified_; 46 JSGraph jsgraph_; 47 GraphReducer reducer_; 48 JSContextSpecialization spec_; 49 }; 50 51 52 TEST(ReduceJSLoadContext) { 53 ContextSpecializationTester t; 54 55 Node* start = t.graph()->NewNode(t.common()->Start(0)); 56 t.graph()->SetStart(start); 57 58 // Make a context and initialize it a bit for this test. 59 Handle<Context> native = t.factory()->NewNativeContext(); 60 Handle<Context> subcontext1 = t.factory()->NewNativeContext(); 61 Handle<Context> subcontext2 = t.factory()->NewNativeContext(); 62 subcontext2->set_previous(*subcontext1); 63 subcontext1->set_previous(*native); 64 Handle<Object> expected = t.factory()->InternalizeUtf8String("gboy!"); 65 const int slot = Context::NATIVE_CONTEXT_INDEX; 66 native->set(slot, *expected); 67 68 Node* const_context = t.jsgraph()->Constant(native); 69 Node* deep_const_context = t.jsgraph()->Constant(subcontext2); 70 Node* param_context = t.graph()->NewNode(t.common()->Parameter(0), start); 71 72 { 73 // Mutable slot, constant context, depth = 0 => do nothing. 74 Node* load = t.graph()->NewNode(t.javascript()->LoadContext(0, 0, false), 75 const_context, const_context, start); 76 Reduction r = t.spec()->Reduce(load); 77 CHECK(!r.Changed()); 78 } 79 80 { 81 // Mutable slot, non-constant context, depth = 0 => do nothing. 82 Node* load = t.graph()->NewNode(t.javascript()->LoadContext(0, 0, false), 83 param_context, param_context, start); 84 Reduction r = t.spec()->Reduce(load); 85 CHECK(!r.Changed()); 86 } 87 88 { 89 // Mutable slot, constant context, depth > 0 => fold-in parent context. 90 Node* load = t.graph()->NewNode( 91 t.javascript()->LoadContext(2, Context::GLOBAL_EVAL_FUN_INDEX, false), 92 deep_const_context, deep_const_context, start); 93 Reduction r = t.spec()->Reduce(load); 94 CHECK(r.Changed()); 95 Node* new_context_input = NodeProperties::GetValueInput(r.replacement(), 0); 96 CHECK_EQ(IrOpcode::kHeapConstant, new_context_input->opcode()); 97 HeapObjectMatcher match(new_context_input); 98 CHECK_EQ(*native, *match.Value()); 99 ContextAccess access = OpParameter<ContextAccess>(r.replacement()); 100 CHECK_EQ(Context::GLOBAL_EVAL_FUN_INDEX, static_cast<int>(access.index())); 101 CHECK_EQ(0, static_cast<int>(access.depth())); 102 CHECK_EQ(false, access.immutable()); 103 } 104 105 { 106 // Immutable slot, constant context, depth = 0 => specialize. 107 Node* load = t.graph()->NewNode(t.javascript()->LoadContext(0, slot, true), 108 const_context, const_context, start); 109 Reduction r = t.spec()->Reduce(load); 110 CHECK(r.Changed()); 111 CHECK(r.replacement() != load); 112 113 HeapObjectMatcher match(r.replacement()); 114 CHECK(match.HasValue()); 115 CHECK_EQ(*expected, *match.Value()); 116 } 117 118 // TODO(titzer): test with other kinds of contexts, e.g. a function context. 119 // TODO(sigurds): test that loads below create context are not optimized 120 } 121 122 123 TEST(ReduceJSStoreContext) { 124 ContextSpecializationTester t; 125 126 Node* start = t.graph()->NewNode(t.common()->Start(0)); 127 t.graph()->SetStart(start); 128 129 // Make a context and initialize it a bit for this test. 130 Handle<Context> native = t.factory()->NewNativeContext(); 131 Handle<Context> subcontext1 = t.factory()->NewNativeContext(); 132 Handle<Context> subcontext2 = t.factory()->NewNativeContext(); 133 subcontext2->set_previous(*subcontext1); 134 subcontext1->set_previous(*native); 135 Handle<Object> expected = t.factory()->InternalizeUtf8String("gboy!"); 136 const int slot = Context::NATIVE_CONTEXT_INDEX; 137 native->set(slot, *expected); 138 139 Node* const_context = t.jsgraph()->Constant(native); 140 Node* deep_const_context = t.jsgraph()->Constant(subcontext2); 141 Node* param_context = t.graph()->NewNode(t.common()->Parameter(0), start); 142 143 { 144 // Mutable slot, constant context, depth = 0 => do nothing. 145 Node* load = 146 t.graph()->NewNode(t.javascript()->StoreContext(0, 0), const_context, 147 const_context, const_context, start, start); 148 Reduction r = t.spec()->Reduce(load); 149 CHECK(!r.Changed()); 150 } 151 152 { 153 // Mutable slot, non-constant context, depth = 0 => do nothing. 154 Node* load = 155 t.graph()->NewNode(t.javascript()->StoreContext(0, 0), param_context, 156 param_context, const_context, start, start); 157 Reduction r = t.spec()->Reduce(load); 158 CHECK(!r.Changed()); 159 } 160 161 { 162 // Immutable slot, constant context, depth = 0 => do nothing. 163 Node* load = 164 t.graph()->NewNode(t.javascript()->StoreContext(0, slot), const_context, 165 const_context, const_context, start, start); 166 Reduction r = t.spec()->Reduce(load); 167 CHECK(!r.Changed()); 168 } 169 170 { 171 // Mutable slot, constant context, depth > 0 => fold-in parent context. 172 Node* load = t.graph()->NewNode( 173 t.javascript()->StoreContext(2, Context::GLOBAL_EVAL_FUN_INDEX), 174 deep_const_context, deep_const_context, const_context, start, start); 175 Reduction r = t.spec()->Reduce(load); 176 CHECK(r.Changed()); 177 Node* new_context_input = NodeProperties::GetValueInput(r.replacement(), 0); 178 CHECK_EQ(IrOpcode::kHeapConstant, new_context_input->opcode()); 179 HeapObjectMatcher match(new_context_input); 180 CHECK_EQ(*native, *match.Value()); 181 ContextAccess access = OpParameter<ContextAccess>(r.replacement()); 182 CHECK_EQ(Context::GLOBAL_EVAL_FUN_INDEX, static_cast<int>(access.index())); 183 CHECK_EQ(0, static_cast<int>(access.depth())); 184 CHECK_EQ(false, access.immutable()); 185 } 186 } 187 188 189 // TODO(titzer): factor out common code with effects checking in typed lowering. 190 static void CheckEffectInput(Node* effect, Node* use) { 191 CHECK_EQ(effect, NodeProperties::GetEffectInput(use)); 192 } 193 194 195 TEST(SpecializeToContext) { 196 ContextSpecializationTester t; 197 198 Node* start = t.graph()->NewNode(t.common()->Start(0)); 199 t.graph()->SetStart(start); 200 201 // Make a context and initialize it a bit for this test. 202 Handle<Context> native = t.factory()->NewNativeContext(); 203 Handle<Object> expected = t.factory()->InternalizeUtf8String("gboy!"); 204 const int slot = Context::NATIVE_CONTEXT_INDEX; 205 native->set(slot, *expected); 206 207 Node* const_context = t.jsgraph()->Constant(native); 208 Node* param_context = t.graph()->NewNode(t.common()->Parameter(0), start); 209 210 { 211 // Check that specialization replaces values and forwards effects 212 // correctly, and folds values from constant and non-constant contexts 213 Node* effect_in = start; 214 Node* load = t.graph()->NewNode(t.javascript()->LoadContext(0, slot, true), 215 const_context, const_context, effect_in); 216 217 218 Node* value_use = 219 t.graph()->NewNode(t.simplified()->ChangeTaggedToInt32(), load); 220 Node* other_load = 221 t.graph()->NewNode(t.javascript()->LoadContext(0, slot, true), 222 param_context, param_context, load); 223 Node* effect_use = other_load; 224 Node* other_use = 225 t.graph()->NewNode(t.simplified()->ChangeTaggedToInt32(), other_load); 226 227 Node* add = t.graph()->NewNode( 228 t.javascript()->Add(LanguageMode::SLOPPY, BinaryOperationHints::Any()), 229 value_use, other_use, param_context, t.jsgraph()->EmptyFrameState(), 230 t.jsgraph()->EmptyFrameState(), other_load, start); 231 232 Node* ret = 233 t.graph()->NewNode(t.common()->Return(), add, effect_use, start); 234 Node* end = t.graph()->NewNode(t.common()->End(1), ret); 235 USE(end); 236 t.graph()->SetEnd(end); 237 238 // Double check the above graph is what we expect, or the test is broken. 239 CheckEffectInput(effect_in, load); 240 CheckEffectInput(load, effect_use); 241 242 // Perform the reduction on the entire graph. 243 GraphReducer graph_reducer(t.main_zone(), t.graph()); 244 JSContextSpecialization spec(&graph_reducer, t.jsgraph(), 245 MaybeHandle<Context>()); 246 graph_reducer.AddReducer(&spec); 247 graph_reducer.ReduceGraph(); 248 249 // Effects should have been forwarded (not replaced with a value). 250 CheckEffectInput(effect_in, effect_use); 251 252 // Use of {other_load} should not have been replaced. 253 CHECK_EQ(other_load, other_use->InputAt(0)); 254 255 Node* replacement = value_use->InputAt(0); 256 HeapObjectMatcher match(replacement); 257 CHECK(match.HasValue()); 258 CHECK_EQ(*expected, *match.Value()); 259 } 260 // TODO(titzer): clean up above test and test more complicated effects. 261 } 262 263 264 TEST(SpecializeJSFunction_ToConstant1) { 265 FunctionTester T( 266 "(function() { var x = 1; function inc(a)" 267 " { return a + x; } return inc; })()"); 268 269 T.CheckCall(1.0, 0.0, 0.0); 270 T.CheckCall(2.0, 1.0, 0.0); 271 T.CheckCall(2.1, 1.1, 0.0); 272 } 273 274 275 TEST(SpecializeJSFunction_ToConstant2) { 276 FunctionTester T( 277 "(function() { var x = 1.5; var y = 2.25; var z = 3.75;" 278 " function f(a) { return a - x + y - z; } return f; })()"); 279 280 T.CheckCall(-3.0, 0.0, 0.0); 281 T.CheckCall(-2.0, 1.0, 0.0); 282 T.CheckCall(-1.9, 1.1, 0.0); 283 } 284 285 286 TEST(SpecializeJSFunction_ToConstant3) { 287 FunctionTester T( 288 "(function() { var x = -11.5; function inc()" 289 " { return (function(a) { return a + x; }); }" 290 " return inc(); })()"); 291 292 T.CheckCall(-11.5, 0.0, 0.0); 293 T.CheckCall(-10.5, 1.0, 0.0); 294 T.CheckCall(-10.4, 1.1, 0.0); 295 } 296 297 298 TEST(SpecializeJSFunction_ToConstant_uninit) { 299 { 300 FunctionTester T( 301 "(function() { if (false) { var x = 1; } function inc(a)" 302 " { return x; } return inc; })()"); // x is undefined! 303 304 CHECK(T.Call(T.Val(0.0), T.Val(0.0)).ToHandleChecked()->IsUndefined()); 305 CHECK(T.Call(T.Val(2.0), T.Val(0.0)).ToHandleChecked()->IsUndefined()); 306 CHECK(T.Call(T.Val(-2.1), T.Val(0.0)).ToHandleChecked()->IsUndefined()); 307 } 308 309 { 310 FunctionTester T( 311 "(function() { if (false) { var x = 1; } function inc(a)" 312 " { return a + x; } return inc; })()"); // x is undefined! 313 314 CHECK(T.Call(T.Val(0.0), T.Val(0.0)).ToHandleChecked()->IsNaN()); 315 CHECK(T.Call(T.Val(2.0), T.Val(0.0)).ToHandleChecked()->IsNaN()); 316 CHECK(T.Call(T.Val(-2.1), T.Val(0.0)).ToHandleChecked()->IsNaN()); 317 } 318 } 319 320 } // namespace compiler 321 } // namespace internal 322 } // namespace v8 323