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