Bug 125377

Summary: REGRESSION: 2x regression on Dromaeo DOM query tests
Product: WebKit Reporter: Ryosuke Niwa <rniwa>
Component: BindingsAssignee: Ryosuke Niwa <rniwa>
Status: RESOLVED FIXED    
Severity: Normal CC: barraclough, benjamin, buildbot, cdumez, commit-queue, esprehn+autocc, fpizlo, ggaren, gyuyoung.kim, koivisto, kondapallykalyan, oliver, rniwa, sam, xinchao.peng
Priority: P2    
Version: 528+ (Nightly build)   
Hardware: Unspecified   
OS: Unspecified   
Attachments:
Description Flags
Fixes the bug
none
De-duped code
none
Added a microbenchmark
none
Perf. test results
none
New watchpoint approach work in progress
fpizlo: review-
Work in progress 2
none
Archive of layout-test-results from webkit-ews-09 for mac-mountainlion-wk2
none
Archive of layout-test-results from webkit-ews-07 for mac-mountainlion
none
Archive of layout-test-results from webkit-ews-12 for mac-mountainlion-wk2
none
Archive of layout-test-results from webkit-ews-01 for mac-mountainlion
none
Patch
none
Reverted unrelated whitespace changes
none
Work in progress 3
none
Fixes the bug
none
Fixed tests none

Ryosuke Niwa
Reported 2013-12-06 20:27:16 PST
We should fix that.
Attachments
Fixes the bug (9.19 KB, patch)
2013-12-06 20:44 PST, Ryosuke Niwa
no flags
De-duped code (9.25 KB, patch)
2013-12-06 20:50 PST, Ryosuke Niwa
no flags
Added a microbenchmark (11.09 KB, patch)
2013-12-07 14:05 PST, Ryosuke Niwa
no flags
Perf. test results (25.12 KB, text/html)
2013-12-07 14:05 PST, Ryosuke Niwa
no flags
New watchpoint approach work in progress (15.64 KB, patch)
2013-12-11 21:42 PST, Ryosuke Niwa
fpizlo: review-
Work in progress 2 (12.61 KB, patch)
2013-12-12 01:01 PST, Ryosuke Niwa
no flags
Archive of layout-test-results from webkit-ews-09 for mac-mountainlion-wk2 (1.67 MB, application/zip)
2013-12-12 02:20 PST, Build Bot
no flags
Archive of layout-test-results from webkit-ews-07 for mac-mountainlion (1.82 MB, application/zip)
2013-12-12 02:49 PST, Build Bot
no flags
Archive of layout-test-results from webkit-ews-12 for mac-mountainlion-wk2 (1.72 MB, application/zip)
2013-12-12 03:06 PST, Build Bot
no flags
Archive of layout-test-results from webkit-ews-01 for mac-mountainlion (1.78 MB, application/zip)
2013-12-12 05:00 PST, Build Bot
no flags
Patch (71.72 KB, patch)
2013-12-12 20:06 PST, Ryosuke Niwa
no flags
Reverted unrelated whitespace changes (71.08 KB, patch)
2013-12-12 20:10 PST, Ryosuke Niwa
no flags
Work in progress 3 (71.99 KB, patch)
2013-12-12 20:56 PST, Ryosuke Niwa
no flags
Fixes the bug (92.03 KB, patch)
2013-12-15 18:25 PST, Ryosuke Niwa
no flags
Fixed tests (61.64 KB, patch)
2013-12-15 19:00 PST, Ryosuke Niwa
no flags
Ryosuke Niwa
Comment 1 2013-12-06 20:44:17 PST
Created attachment 218645 [details] Fixes the bug
Ryosuke Niwa
Comment 2 2013-12-06 20:50:42 PST
Created attachment 218646 [details] De-duped code
Filip Pizlo
Comment 3 2013-12-06 20:53:01 PST
Comment on attachment 218645 [details] Fixes the bug View in context: https://bugs.webkit.org/attachment.cgi?id=218645&action=review Can you create microbenchmarks for the common case and the worst case? I know that Dromaeo already has such a microbenchmark but it might be good to have something more surgical. And I'd like to see the microbenchmark for the case where we have to clearImpurePropertySlots. My gut tells me that you should instead be having impure property slot caches register a watchpoint that add/removeDocumentNamedItem fires. That way the cost of repeated add/removeDocumentNamedItem won't have O(numWorlds) performance. I think we need to determine if this approach is performant enough. Also, do we already have test coverage for this? I don't think we have test coverage for the remove case, and actually, I'm not sure that the remove case needs to clearImpurePropertySlots. > Source/JavaScriptCore/runtime/JSObject.h:126 > + // FIXME: We should be able to clear just a single slot instead. File a bug for the FIXME, and reference the bug. > Source/JavaScriptCore/runtime/JSObject.h:130 > + void clearImpurePropertySlots(VM& vm) > + { > + setStructure(vm, Structure::impureNameGetterTransition(vm, structure())); > + } This method should be out-of-line, in JSObject.cpp. > Source/WebCore/html/HTMLDocument.cpp:336 > m_documentNamedItem.add(name, item, *this); > + > + Vector<Ref<DOMWrapperWorld>> worlds; > + ScriptController::getAllWorlds(worlds); > + for (size_t i = 0; i < worlds.size(); ++i) > + clearImpurePropertySlots(worlds[i].get(), this); How often does this happen in practice? How often can it happen in the worst case? How much slower does this make us when that worst case happens? > Source/WebCore/html/HTMLDocument.cpp:346 > m_documentNamedItem.remove(name, item); > + > + Vector<Ref<DOMWrapperWorld>> worlds; > + ScriptController::getAllWorlds(worlds); > + for (size_t i = 0; i < worlds.size(); ++i) > + clearImpurePropertySlots(worlds[i].get(), this); Ditto.
Ryosuke Niwa
Comment 4 2013-12-07 14:03:25 PST
(In reply to comment #3) > (From update of attachment 218645 [details]) > View in context: https://bugs.webkit.org/attachment.cgi?id=218645&action=review > > Can you create microbenchmarks for the common case and the worst case? We already have one: PerformanceTests/Bindings/get-element-by-id.html > And I'd like to see the microbenchmark for the case where we have to clearImpurePropertySlots. I've created PerformanceTests/Bindings/update-name-getter.html > My gut tells me that you should instead be having impure property slot caches register a watchpoint that add/removeDocumentNamedItem fires. That way the cost of repeated add/removeDocumentNamedItem won't have O(numWorlds) performance. That'll be great. Can we talk about this in person sometime next week? It turns out that my current patch improves get-element-by-id.html by 3x but slows down PerformanceTests/Bindings/update-name-getter.html by 3x so it's not that great as is. > I think we need to determine if this approach is performant enough. Unfortunately, it isn't as you've suspected :(
Ryosuke Niwa
Comment 5 2013-12-07 14:05:17 PST
Created attachment 218668 [details] Added a microbenchmark
Ryosuke Niwa
Comment 6 2013-12-07 14:05:51 PST
Created attachment 218669 [details] Perf. test results
Ryosuke Niwa
Comment 7 2013-12-11 21:42:04 PST
*** Bug 122628 has been marked as a duplicate of this bug. ***
Ryosuke Niwa
Comment 8 2013-12-11 21:42:54 PST
Created attachment 219042 [details] New watchpoint approach work in progress
Filip Pizlo
Comment 9 2013-12-11 23:42:57 PST
Comment on attachment 219042 [details] New watchpoint approach work in progress View in context: https://bugs.webkit.org/attachment.cgi?id=219042&action=review > Source/JavaScriptCore/bytecode/StructureStubClearingWatchpoint.cpp:58 > + if (m_watchpointSetToUnregisterAtDestruction) > + m_codeBlock->vm()->unregisterWatchpointSetForImpureProperty(m_propertyNameForUnregisteringWatchpointSet, *m_watchpointSetToUnregisterAtDestruction); > } This isn't needed. > Source/JavaScriptCore/bytecode/StructureStubClearingWatchpoint.h:34 > +#include "Identifier.h" This isn't needed. > Source/JavaScriptCore/bytecode/StructureStubClearingWatchpoint.h:96 > + void unregisterWatchpointSetAtDestruction(const Identifier& propertyName, InlineWatchpointSet& watchpointSet) > + { > + ASSERT(!m_watchpointSetToUnregisterAtDestruction); > + m_watchpointSetToUnregisterAtDestruction = &watchpointSet; > + // FIXME: We should store AtomicString once we've merged it with Identifier. > + m_propertyNameForUnregisteringWatchpointSet = propertyName.string(); > + } > + Get rid of this. > Source/JavaScriptCore/bytecode/StructureStubClearingWatchpoint.h:113 > + InlineWatchpointSet* m_watchpointSetToUnregisterAtDestruction; > + String m_propertyNameForUnregisteringWatchpointSet; Get rid of this. > Source/JavaScriptCore/bytecode/StructureStubInfo.h:41 > +#include <wtf/text/WTFString.h> Get rid of this. > Source/JavaScriptCore/bytecode/StructureStubInfo.h:224 > + > + void unregisterWatchpointSetAtDestruction(const Identifier& propertyName, InlineWatchpointSet& watchpointSet) > + { > + watchpoints->unregisterWatchpointSetAtDestruction(propertyName, watchpointSet); > + } You don't need this. > Source/JavaScriptCore/jit/Repatch.cpp:260 > + if (prototypeStructure->typeInfo().newImpurePropertyFiresWatchpoints()) { > + InlineWatchpointSet& set = currStructure->transitionWatchpointSet(); > + stubInfo.unregisterWatchpointSetAtDestruction(propertyName, set); > + vm->registerWatchpointSetForImpureProperty(propertyName, set); > + } Don't use the transitionWatchpointSet(). That set is for transitions. You're not doing transitions. Instead you should say: if (prototypeStructure->typeInfo().newImpurePropertyFiresWatchpoints()) { Watchpoint* watchpoint = stubInfo.addWatchpoint(codeBlock); vm->registerWatchpointForImpureProperty(propertyName, watchpoint); } What you're doing is basically wrong, or at least highly circuitous. It's wrong because: - A watchpoint set should be associate with some conceptual event. The structure's transition watchpoint set (currStructure->transitionWatchpointSet()) corresponds to a transition. It's fired whenever there is a transition, and people register watchpoints on transitions with that watchpoint set. You're reusing that set for your own purposes, which don't involve transitions. That's just plain wrong. You're making it so that if an impure property gets added, a bunch of listeners will think there had been a transition (even though no transition occurred). You'll also fire the impure property thing whenever a transition occurs, even though a transition doesn't necessarily imply an impure property. You're conflating unrelated things. - As a result of your reuse of the transition watchpoint set, you create a mess for yourself trying to manage the lifecycle of the transition watchpoint set. It's true that this is hard, but it's only hard because you're using an existing set instead of creating a new one. - You shouldn't be using an InlineWatchpointSet at all. Even if what you were doing wasn't outright wrong, it's clear that you're creating a *ton* of infrastructure for lifecycle management. That infrastructure already exists. It's called a watchpoint set. The reason why you're unable to leverage that existing infrastructure is that you're reusing an existing watchpoint set which has its lifecycle tied to a GC'd object. So, yeah, that's going to be hard. But you don't need to use that watchpoint set. Just create your own watchpoint set and you won't have to write half the code in this patch. > Source/JavaScriptCore/runtime/VM.cpp:795 > +void VM::registerWatchpointSetForImpureProperty(const Identifier& propertyName, InlineWatchpointSet& watchpointSet) The signature of this method should be: void VM::registerWatchpointForImpureProperty(const Identifier& propertyName, Watchpoint* watchpoint) > Source/JavaScriptCore/runtime/VM.cpp:798 > + auto result = m_impurePropertyWatchpointSetSets.add(propertyName.string(), HashSet<InlineWatchpointSet*>()); > + result.iterator->value.add(&watchpointSet); This should check if the map has a WatchpointSet* for the property name. It should create one if one didn't already exit. Then it should add the watchpoint to that set. Even if there was a way to make your approach work, it just feels awfully wrong: you have a map of sets of sets! The whole point of a watchpoint set is to be a set of watchpoints. You're creating a set of watchpoint sets, where those sets are borrowed from some unrelated Structure*, and then you're placing watchpoints into those sets. :-/ That's highly unusual. > Source/JavaScriptCore/runtime/VM.cpp:809 > +void VM::unregisterWatchpointSetForImpureProperty(const String& propertyName, InlineWatchpointSet& watchpointSet) > +{ > + auto result = m_impurePropertyWatchpointSetSets.find(propertyName); > + ASSERT(result != m_impurePropertyWatchpointSetSets.end()); > + if (result->value.size() == 1) > + m_impurePropertyWatchpointSetSets.remove(result); > + else > + result->value.remove(&watchpointSet); > +} Kill this method. > Source/JavaScriptCore/runtime/VM.cpp:817 > + for (auto it = result->value.begin(), end = result->value.end(); it != end; ++it) > + (*it)->fireAll(); You don't need this loop, since result->value should be a RefPtr<WatchpointSet>. Then you call fireAll(). Then you should do m_impurePropertyWatchpointSets.remove(propertyName). The reason for this is that WatchpointSets are one-shot. So, after you fire it, it invalidates all watchpoints and then invalidates itself. fireAll() is idempotent: fireAll() a second time will do nothing. Also removing it saves memory. > Source/JavaScriptCore/runtime/VM.h:523 > + HashMap<String, HashSet<InlineWatchpointSet*>> m_impurePropertyWatchpointSetSets; This should be: HashMap<String, RefPtr<WatchpointSet>> m_impurePropertyWatchpointSets;
Ryosuke Niwa
Comment 10 2013-12-12 01:01:25 PST
Created attachment 219055 [details] Work in progress 2
Build Bot
Comment 11 2013-12-12 02:20:37 PST
Comment on attachment 219055 [details] Work in progress 2 Attachment 219055 [details] did not pass mac-wk2-ews (mac-wk2): Output: http://webkit-queues.appspot.com/results/47008368 New failing tests: js/dom/dfg-prototype-chain-caching-with-impure-get-own-property-slot-traps.html
Build Bot
Comment 12 2013-12-12 02:20:41 PST
Created attachment 219062 [details] Archive of layout-test-results from webkit-ews-09 for mac-mountainlion-wk2 The attached test failures were seen while running run-webkit-tests on the mac-wk2-ews. Bot: webkit-ews-09 Port: mac-mountainlion-wk2 Platform: Mac OS X 10.8.5
Build Bot
Comment 13 2013-12-12 02:49:14 PST
Comment on attachment 219055 [details] Work in progress 2 Attachment 219055 [details] did not pass mac-ews (mac): Output: http://webkit-queues.appspot.com/results/48138231 New failing tests: js/dom/dfg-prototype-chain-caching-with-impure-get-own-property-slot-traps.html
Build Bot
Comment 14 2013-12-12 02:49:18 PST
Created attachment 219065 [details] Archive of layout-test-results from webkit-ews-07 for mac-mountainlion The attached test failures were seen while running run-webkit-tests on the mac-ews. Bot: webkit-ews-07 Port: mac-mountainlion Platform: Mac OS X 10.8.5
Build Bot
Comment 15 2013-12-12 03:06:51 PST
Comment on attachment 219055 [details] Work in progress 2 Attachment 219055 [details] did not pass mac-wk2-ews (mac-wk2): Output: http://webkit-queues.appspot.com/results/48108242 New failing tests: js/dom/dfg-prototype-chain-caching-with-impure-get-own-property-slot-traps.html
Build Bot
Comment 16 2013-12-12 03:06:57 PST
Created attachment 219067 [details] Archive of layout-test-results from webkit-ews-12 for mac-mountainlion-wk2 The attached test failures were seen while running run-webkit-tests on the mac-wk2-ews. Bot: webkit-ews-12 Port: mac-mountainlion-wk2 Platform: Mac OS X 10.8.5
Build Bot
Comment 17 2013-12-12 05:00:22 PST
Comment on attachment 219055 [details] Work in progress 2 Attachment 219055 [details] did not pass mac-ews (mac): Output: http://webkit-queues.appspot.com/results/48088262 New failing tests: js/dom/dfg-prototype-chain-caching-with-impure-get-own-property-slot-traps.html
Build Bot
Comment 18 2013-12-12 05:00:27 PST
Created attachment 219076 [details] Archive of layout-test-results from webkit-ews-01 for mac-mountainlion The attached test failures were seen while running run-webkit-tests on the mac-ews. Bot: webkit-ews-01 Port: mac-mountainlion Platform: Mac OS X 10.8.5
Ryosuke Niwa
Comment 19 2013-12-12 20:06:44 PST
Ryosuke Niwa
Comment 20 2013-12-12 20:10:29 PST
Created attachment 219145 [details] Reverted unrelated whitespace changes
Darin Adler
Comment 21 2013-12-12 20:34:22 PST
Comment on attachment 219145 [details] Reverted unrelated whitespace changes View in context: https://bugs.webkit.org/attachment.cgi?id=219145&action=review > Source/JavaScriptCore/runtime/VM.cpp:785 > + auto result = m_impurePropertyWatchpointSets.find(propertyName); > + if (result == m_impurePropertyWatchpointSets.end()) > + return; > + > + ASSERT(result->value); > + result->value->fireAll(); > + > + m_impurePropertyWatchpointSets.remove(result); It is important to leave it in m_impurePropertyWatchpointSets while calling fireAll? If not, we could use take here instead of find/remove.
Ryosuke Niwa
Comment 22 2013-12-12 20:43:21 PST
Comment on attachment 219145 [details] Reverted unrelated whitespace changes View in context: https://bugs.webkit.org/attachment.cgi?id=219145&action=review This patch is still wrong for this case: document.foo = function () {}; function f() { return document.foo; } for (var i = 0; i < 400; ++i) { if (i == 350) { var img = new Image(); img.name = "foo"; document.body.appendChild(img); } f(); // should be the image element for the last 50 runs. } >> Source/JavaScriptCore/runtime/VM.cpp:785 >> + m_impurePropertyWatchpointSets.remove(result); > > It is important to leave it in m_impurePropertyWatchpointSets while calling fireAll? If not, we could use take here instead of find/remove. That's a good point. Will do.
Ryosuke Niwa
Comment 23 2013-12-12 20:56:07 PST
Created attachment 219148 [details] Work in progress 3
Ryosuke Niwa
Comment 24 2013-12-15 18:25:59 PST
Created attachment 219286 [details] Fixes the bug
Filip Pizlo
Comment 25 2013-12-15 18:31:16 PST
Comment on attachment 219286 [details] Fixes the bug View in context: https://bugs.webkit.org/attachment.cgi?id=219286&action=review R=me on the code changes but you should make the tests do the right thing for concurrent JIT. Otherwise the DFG tests will turn into flaky tests, instead of failing reliably, if someone breaks this again in the future. We don't want flaky tests. > LayoutTests/js/dom/dfg-prototype-chain-caching-with-impure-get-own-property-slot-traps-2.html:25 > +description("Tests what happens when you make prototype chain accesses with impure GetOwnPropertySlot traps in the way."); > + > +var obj = {}; > +obj.__proto__ = document; > + > +function f() { > + return obj.getElementsByTagName; > +} > + > +var expected = "\"function\""; > +for (var i = 0; i < 400; ++i) { > + if (i == 350) { > + var img = new Image(); > + img.name = "getElementsByTagName"; > + document.body.appendChild(img); > + expected = "\"object\""; > + } > + shouldBe("typeof f()", expected); > +} Can you rewrite these tests to use dfgIncrement? That will make them relevant even in the case of concurrent JIT. Or you can use dfgShouldBe(). > LayoutTests/js/dom/dfg-prototype-chain-caching-with-impure-get-own-property-slot-traps-4.html:26 > +var expected = "\"function\""; > +for (var i = 0; i < 400; ++i) { > + if (i == 350) { > + var img = new Image(); > + img.name = "foo"; > + document.body.appendChild(img); > + expected = "\"object\""; > + } > + shouldBe("typeof f()", expected); > +} Ditto. > LayoutTests/js/dom/prototype-chain-caching-with-impure-get-own-property-slot-traps-2.html:25 > +var expected = "\"function\""; > +for (var i = 0; i < 40; ++i) { > + if (i == 35) { > + var img = new Image(); > + img.name = "getElementsByTagName"; > + document.body.appendChild(img); > + expected = "\"object\""; > + } > + shouldBe("typeof f()", expected); > +} Ditto.
Filip Pizlo
Comment 26 2013-12-15 18:32:17 PST
(In reply to comment #25) > (From update of attachment 219286 [details]) > View in context: https://bugs.webkit.org/attachment.cgi?id=219286&action=review > > R=me on the code changes but you should make the tests do the right thing for concurrent JIT. Otherwise the DFG tests will turn into flaky tests, instead of failing reliably, if someone breaks this again in the future. We don't want flaky tests. > > > LayoutTests/js/dom/dfg-prototype-chain-caching-with-impure-get-own-property-slot-traps-2.html:25 > > +description("Tests what happens when you make prototype chain accesses with impure GetOwnPropertySlot traps in the way."); > > + > > +var obj = {}; > > +obj.__proto__ = document; > > + > > +function f() { > > + return obj.getElementsByTagName; > > +} > > + > > +var expected = "\"function\""; > > +for (var i = 0; i < 400; ++i) { > > + if (i == 350) { > > + var img = new Image(); > > + img.name = "getElementsByTagName"; > > + document.body.appendChild(img); > > + expected = "\"object\""; > > + } > > + shouldBe("typeof f()", expected); > > +} > > Can you rewrite these tests to use dfgIncrement? That will make them relevant even in the case of concurrent JIT. > > Or you can use dfgShouldBe(). > > > LayoutTests/js/dom/dfg-prototype-chain-caching-with-impure-get-own-property-slot-traps-4.html:26 > > +var expected = "\"function\""; > > +for (var i = 0; i < 400; ++i) { > > + if (i == 350) { > > + var img = new Image(); > > + img.name = "foo"; > > + document.body.appendChild(img); > > + expected = "\"object\""; > > + } > > + shouldBe("typeof f()", expected); > > +} > > Ditto. > > > LayoutTests/js/dom/prototype-chain-caching-with-impure-get-own-property-slot-traps-2.html:25 > > +var expected = "\"function\""; > > +for (var i = 0; i < 40; ++i) { > > + if (i == 35) { > > + var img = new Image(); > > + img.name = "getElementsByTagName"; > > + document.body.appendChild(img); > > + expected = "\"object\""; > > + } > > + shouldBe("typeof f()", expected); > > +} > > Ditto. Well I guess this last test isn't a DFG test so it doesn't need dfgShouldBe()/dfgIncrement(). But the DFG tests need some story for if the concurrent JIT decides to take longer to compile the code.
Ryosuke Niwa
Comment 27 2013-12-15 19:00:17 PST
Created attachment 219288 [details] Fixed tests
WebKit Commit Bot
Comment 28 2013-12-15 21:53:13 PST
Comment on attachment 219288 [details] Fixed tests Clearing flags on attachment: 219288 Committed r160628: <http://trac.webkit.org/changeset/160628>
WebKit Commit Bot
Comment 29 2013-12-15 21:53:18 PST
All reviewed patches have been landed. Closing bug.
Geoffrey Garen
Comment 30 2013-12-16 11:30:58 PST
Comment on attachment 219288 [details] Fixed tests View in context: https://bugs.webkit.org/attachment.cgi?id=219288&action=review > Source/JavaScriptCore/ChangeLog:23 > + (JSC::repatchByIdSelfAccess): Throw away the byte code when a new impure property is added on any > + object in the prototype chain via StructureStubClearingWatchpoint. See below. > Source/WebCore/ChangeLog:10 > + The bug was caused by JSC not JIT'ing property accesses on document because of its having > + custom named getter (named properties). This resulted in resolution of methods on document > + such as getElementById to happen inside the interpreter. This isn't right. Previously, we did JIT those property accesses, we just failed to optimize them. They didn't run in the interpreter. > LayoutTests/ChangeLog:8 > + Added more regression tests for throwing away byte code when a new named property appears. You don't really mean "throwing away byte code", do you? Are you actually evicting the bytecode from the bytecode cache -- or are you just throwing away a certain optimized code block?
Peng Xinchao
Comment 31 2013-12-22 18:11:17 PST
I don't make sure the patch's result. But the result of my testing In x86_64 GTK , the patch don't improve DROMAEO/DOM performance.
Ryosuke Niwa
Comment 32 2013-12-23 12:02:01 PST
(In reply to comment #31) > I don't make sure the patch's result. But the result of my testing In x86_64 GTK , the patch don't improve DROMAEO/DOM performance. We're definitely seeing 2x improvement on Dromaeo. Perhaps you don't have JIT enabled?
Note You need to log in before you can comment on or make changes to this bug.