Changeset 187347 in webkit
- Timestamp:
- Jul 24, 2015, 11:23:13 AM (11 years ago)
- Location:
- trunk/Source/JavaScriptCore
- Files:
-
- 1 added
- 2 edited
-
ChangeLog (modified) (1 diff)
-
dfg/DFGSafeToExecute.h (modified) (3 diffs)
-
tests/stress/multi-get-by-offset-hoist-around-structure-check.js (added)
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/JavaScriptCore/ChangeLog
r187283 r187347 1 2015-07-23 Filip Pizlo <fpizlo@apple.com> 2 3 DFG::safeToExecute() is wrong for MultiGetByOffset, doesn't consider the structures of the prototypes that get loaded from 4 https://bugs.webkit.org/show_bug.cgi?id=147250 5 6 Reviewed by Geoffrey Garen. 7 8 This fixes a nasty - but currently benign - bug in DFG::safeToExecute(). That function 9 will tell you if hoisting a node to some point is safe in the sense that the node will 10 not crash the VM if it executes at that point. A node may be unsafe to execute if we 11 cannot prove that at that point, the memory it is loading is not garbage. This is a 12 necessarily loose notion - for example it's OK to hoist a load if we haven't proved 13 that the load makes semantic sense at that point, since anyway the place where the node 14 did get used will still be guarded by any such semantic checks. But because we may also 15 hoist uses of the load, we need to make sure that it doesn't produce a garbage value. 16 Also, we need to ensure that the load won't trap. Hence safeToExecute() returns true 17 anytime we can be sure that a node will not produce a garbage result (i.e. a malformed 18 JSValue or object pointer) and will not trap when executed at the point in question. 19 20 The bug is that this verification isn't performed for the loads from prototypes inside 21 MultiGetByOffset. DFG::ByteCodeParser will guard MultiGetByOffset with CheckStructure's 22 on the prototypes. So, hypothetically, you might end up hoisting a MultiGetByOffset 23 above those structure checks, which would mean that we might load a value from a memory 24 location without knowing that the location is valid. It might then return the value 25 loaded. 26 27 This never happens in practice. Those structure checks are more hoistable that the 28 MultiGetByOffset, since they read a strict subset of the MultiGetByOffset's abstract 29 heap reads. Also, we hoist in program order. So, those CheckStructure's will always be 30 hoisted before the MultiGetByOffset gets hoisted. 31 32 But we should fix this anyway. DFG::safeToExecute() has a clear definition of what a 33 "true" return means for IR transformations, and it fails in satisfying that definition 34 for MultiGetByOffset. 35 36 There are various approaches we can use for making this safe. I considered two: 37 38 1) Have MultiGetByOffset refer to the prototypes it is loading from in IR, so that we 39 can check if it's safe to load from them. 40 41 2) Turn off MultiGetByOffset hoisting when it will emit loads from prototypes, and the 42 prototype structure isn't being watched. 43 44 I ended up using (2), because it will be the most natural solution once I finish 45 https://bugs.webkit.org/show_bug.cgi?id=146929. Already now, it's somewhat more natural 46 than (1) since that requires more extensive IR changes. Also, (2) will give us what we 47 want in *most* cases: we will usually watch the prototype structure, and we will 48 usually constant-fold loads from prototypes. Both of these usually-true things would 49 have to become false for MultiGetByOffset hoisting to be disabled by this change. 50 51 This change also adds my attempt at a test, though it's not really a test of this bug. 52 This bug is currently benign. But, the test does at least trigger the logic to run, 53 which is better than nothing. 54 55 * dfg/DFGSafeToExecute.h: 56 (JSC::DFG::safeToExecute): 57 * tests/stress/multi-get-by-offset-hoist-around-structure-check.js: Added. 58 (foo): 59 1 60 2015-07-23 Sukolsak Sakshuwong <sukolsak@gmail.com> 2 61 -
trunk/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
r185239 r187347 100 100 // Determines if it's safe to execute a node within the given abstract state. This may 101 101 // return false conservatively. If it returns true, then you can hoist the given node 102 // up to the given point and expect that it will not crash. This doesn't guarantee that 103 // the node will produce the result you wanted other than not crashing. 102 // up to the given point and expect that it will not crash. It also guarantees that the 103 // node will not produce a malformed JSValue or object pointer when executed in the 104 // given state. But this doesn't guarantee that the node will produce the result you 105 // wanted. For example, you may have a GetByOffset from a prototype that only makes 106 // semantic sense if you've also checked that some nearer prototype doesn't also have 107 // a property of the same name. This could still return true even if that check hadn't 108 // been performed in the given abstract state. That's fine though: the load can still 109 // safely execute before that check, so long as that check continues to guard any 110 // user-observable things done to the loaded value. 104 111 template<typename AbstractStateType> 105 112 bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node) … … 256 263 case ConstantStoragePointer: 257 264 case Check: 258 case MultiGetByOffset:259 265 case MultiPutByOffset: 260 266 case ValueRep: … … 334 340 } 335 341 342 case MultiGetByOffset: { 343 // We can't always guarantee that the MultiGetByOffset is safe to execute if it 344 // contains loads from prototypes. We know that it won't load from those prototypes if 345 // we watch the mutability of the properties being loaded. So, here we try to 346 // constant-fold prototype loads, and if that fails, we claim that we cannot hoist. We 347 // also know that a load is safe to execute if we are watching the prototype's 348 // structure. 349 for (const GetByIdVariant& variant : node->multiGetByOffsetData().variants) { 350 if (!variant.alternateBase()) { 351 // It's not a prototype load. 352 continue; 353 } 354 355 JSValue base = variant.alternateBase(); 356 FrozenValue* frozen = graph.freeze(base); 357 if (state.structureClobberState() == StructuresAreWatched 358 && frozen->structure()->dfgShouldWatch() 359 && frozen->structure()->isValidOffset(variant.offset())) { 360 // We're already watching that it's safe to load from this. 361 continue; 362 } 363 364 JSValue constantResult = graph.tryGetConstantProperty( 365 variant.alternateBase(), variant.baseStructure(), variant.offset()); 366 if (!constantResult) { 367 // Couldn't constant-fold a prototype load. Therefore, we shouldn't hoist 368 // because the safety of the load depends on structure checks on the prototype, 369 // and we're too cheap to verify that here. 370 return false; 371 } 372 373 // Otherwise, we will either: 374 // - Not load from the prototype because constant folding will succeed in the 375 // backend, or 376 // - Emit invalid code that fails watchpoint checks. This could happen if the 377 // property becomes mutable after this point, since we already set the watchpoint 378 // above. This case is OK since the code will fail watchpoint checks and never 379 // get installed. 380 } 381 return true; 382 } 383 336 384 case LastNodeType: 337 385 RELEASE_ASSERT_NOT_REACHED();
Note:
See TracChangeset
for help on using the changeset viewer.