On Tue, 03 Dec 2013 13:58:29 +0100, Mads Sig Ager <ag...@chromium.org> wrote:Very nice document.
the Oilpan project (replacing reference counting in Blink with a tracing garbage collector) has been >on a branch for 6 months and things are looking good. Therefore, we would like to move Oilpan >development to Blink trunk. In preparation for that, here is a design document for the Oilpan system >including a guide to write code for Blink with Oilpan.
https://docs.google.com/document/d/1y7_0ni0E_kxvrah-QtnreMlzCDKN3QP4BN1Aw7eSLfY/edit?usp=sharing
Comments and questions are very welcome.
It is true that GC systems do not have prompt finalization guarantees. However even in the current system you loose these guarantees as soon as a single node of your DOM tree gets a JavaScript wrapper because lifetime of this wrapper and by transitive closure the whole tree is controlled by JS VM GC.
Hence we think situation does not change to the worse in most important real world scenarios.
> What has changed since and what type of changes to the programming model to you envision?We changed to conservative stack scanning from precise stack scanning.
Precise stack scanning required making each pointer in each activation frame to be made known to GC via a special smart pointer (aka Handle). After multiple experiments we determined that with the current state of C++ programming language and C++ compilers it is impossible to implement precise stack scanning for C++ code in a portable way that without overhead.This means Handle/Result/HandleScope things you saw before are not needed anymore and nether cognitive nor performance penalty was introduced.So I would say there is almost no change in the way you would write code. For local references you use raw pointers and things just work.
Makes sense and seems entirely reasonable. Could you point out a
typical class in the branch that might serve as a good example and
that you think demonstrates the model well? The examples in the doc
are great but getting a little more context would help.
1) If you're holding the value on the stack (e.g., in a local variable), you always use a raw pointer (i.e, Foo* or Foo&).
2) If you're holding the value as a member variable of another HeapAllocated object, you use either Member<Foo> or WeakMember<Foo> depending on whether you want a strong or a weak reference.
3) If you're holding the value as a member variable of a non-HeapAllocated object, you use Persistent<Foo>, which is a strong reference. (Maybe we should have WeakPersistent too?)
There are some other changes:
A) Objects that subclass HeapAllocated need to implement a |trace| function that visits each of their members so the GC can trace the object's pointers to other objects.
B) Objects are no longer finalized in a deterministic order, which means we need to be a bit more careful in how we write destructors for HeapAllocated objects. Specifically, we shouldn't access other HeapAllocated objects in destructors of HeapAllocated objects.
I understand on your branch you took an incremental approach using something which melded RefCounted and HeapAllocated. Will that exist on trunk?
Hi,
Just a curious question :)
On Tue 03 Dec 2013 23:54, Vyacheslav Egorov <veg...@chromium.org> writes:Clearly it's possible to do precise stack scanning, as you already
> We changed to conservative stack scanning from precise stack scanning.
>
> Precise stack scanning required making each pointer in each activation
> frame to be made known to GC via a special smart pointer (aka Handle).
> After multiple experiments we determined that with the current state of
> C++ programming language and C++ compilers it is impossible to implement
> precise stack scanning for C++ code in a portable way that without
> overhead.
>
> This means Handle/Result/HandleScope things you saw before are not
> needed anymore and nether cognitive nor performance penalty was
> introduced.
implemented it, and it is what is done in V8 itself. (I understand that
would also make it possible to compact; losing that must have been quite
irritating!)
But do I understand correctly that it was just too slow?
What was the penalty?
By "the state of the C++ compilers" you mean that there are
handle-related optimizations that the compilers could have made but
aren't making?
To unsubscribe from this group and stop receiving emails from it, send an email to blink-dev+...@chromium.org.
One question. Will Oilpan effect code related to OwnPtr also? I mean that will we manage OwnPtr off the olipan heap like as-is?
We did not get performance measurements for ARM yet. I tried to get the branch building for Android a couple of weeks ago and failed because I couldn't get a checkout of a version old enough to be compatible with our oilpan Blink branch. We will make sure to get coverage on all platforms while developing under a compile-time flag so we have full information before turning this on and shipping it.
Thanks Eric!I understand and agree with your concerns. We will be responsible owners of Oilpan and do all we can to spread knowledge about it.
On Sun, Dec 8, 2013 at 11:13 PM, Mads Sig Ager <ag...@chromium.org> wrote:
Thanks Eric!I understand and agree with your concerns. We will be responsible owners of Oilpan and do all we can to spread knowledge about it.Hi Mads,Looking at the slides and reading this discussion, it appears there are 6 different ways to write a pointer in Oilpan. And also you have to write a (somewhat non-trivial I guess?) trace() method.It wasn't that clear from reading the slides what the consequences of doing it wrong are, or what tools we have to make sure we don't mess it up, so I figured I would just ask. Is it as complicated to get right as it sounds? I do agree that it is probably no more complicated than the existing system, but it would be even nicer to be less complicated..
On Mon, Dec 9, 2013 at 6:59 PM, Chris Harrelson <chri...@chromium.org> wrote:
On Sun, Dec 8, 2013 at 11:13 PM, Mads Sig Ager <ag...@chromium.org> wrote:
Thanks Eric!I understand and agree with your concerns. We will be responsible owners of Oilpan and do all we can to spread knowledge about it.Hi Mads,Looking at the slides and reading this discussion, it appears there are 6 different ways to write a pointer in Oilpan. And also you have to write a (somewhat non-trivial I guess?) trace() method.It wasn't that clear from reading the slides what the consequences of doing it wrong are, or what tools we have to make sure we don't mess it up, so I figured I would just ask. Is it as complicated to get right as it sounds? I do agree that it is probably no more complicated than the existing system, but it would be even nicer to be less complicated..I don't find it particularly complicated and we have made it as simple as we could. As Haraken points out in his slides, there are clear rules on when to use which type of pointer. Also, I find that there are a lot of cases where it actually clarifies the code. Currently in the code, when you have a raw pointer you do not know if it is a weak pointer or if it is only raw in order to break a ref cycle. With the Member/WeakMember distinction it is now clear what the meaning of the pointer is. Either it is a strong traced pointer or it is a weak pointer that does not keep what it is pointing to alive and which will be cleared when what is being pointed to is no longer reachable.
It sounds complicated to have to write a trace method. However, in reality, most of the trace methods just trace all of the members that have pointers to other heap objects and have the overall form:void trace(Visitor* visitor){visitor->trace(m_firstMember);visitor->trace(m_secondMember);...}Because this is so stylized we are going to write a clang plugin to verify that all fields of a class that contains Member or WeakMember in its type has to be traced in the trace method. Tracing correctly is very important. If you get it wrong the garbage collector will reuse the memory and you get a use-after-free situation. Therefore, the clang plugin is a high priority for us to help developers avoid this pitfall.There are other classes of problems that you can get into. Using Persistent in something that is in the oilpan heap is disallowed (because it can lead to leaks - you should use Member and trace the pointer in the trace method). Again, this is something that can be detected with the clang plugin.
Overall, it is hard to answer the question of "is this more complicated than before". There are new types and you do have to understand what they mean and how to use them. However, we have designed the system so there are clear rules about which type to use when. We are going to write clang plugins to verify the usage of the types. I guess the real question is if the benefits (no need to worry about cycles, fewer dangling pointers, simplified language bindings) are worth the additional cognitive load. In my mind the answer to that question is a clear yes. :-)
On Mon, Dec 9, 2013 at 10:28 AM, Mads Sig Ager <ag...@chromium.org> wrote:
On Mon, Dec 9, 2013 at 6:59 PM, Chris Harrelson <chri...@chromium.org> wrote:
On Sun, Dec 8, 2013 at 11:13 PM, Mads Sig Ager <ag...@chromium.org> wrote:
Thanks Eric!I understand and agree with your concerns. We will be responsible owners of Oilpan and do all we can to spread knowledge about it.Hi Mads,Looking at the slides and reading this discussion, it appears there are 6 different ways to write a pointer in Oilpan. And also you have to write a (somewhat non-trivial I guess?) trace() method.It wasn't that clear from reading the slides what the consequences of doing it wrong are, or what tools we have to make sure we don't mess it up, so I figured I would just ask. Is it as complicated to get right as it sounds? I do agree that it is probably no more complicated than the existing system, but it would be even nicer to be less complicated..I don't find it particularly complicated and we have made it as simple as we could. As Haraken points out in his slides, there are clear rules on when to use which type of pointer. Also, I find that there are a lot of cases where it actually clarifies the code. Currently in the code, when you have a raw pointer you do not know if it is a weak pointer or if it is only raw in order to break a ref cycle. With the Member/WeakMember distinction it is now clear what the meaning of the pointer is. Either it is a strong traced pointer or it is a weak pointer that does not keep what it is pointing to alive and which will be cleared when what is being pointed to is no longer reachable.The Member/WeakMember distinction I can see being inevitable. The part that worries me most is the other flavors of pointer depending on whether the pointer destination is on/off heap, or the pointer sits in the stack.From your commentary below, it sounds like there are several cases where if you pick the wrong one you can have a serious memory error? I'm also wondering if there is a way to better utilize the type system to disallow some categories of bad usage.
It sounds complicated to have to write a trace method. However, in reality, most of the trace methods just trace all of the members that have pointers to other heap objects and have the overall form:void trace(Visitor* visitor){visitor->trace(m_firstMember);visitor->trace(m_secondMember);...}Because this is so stylized we are going to write a clang plugin to verify that all fields of a class that contains Member or WeakMember in its type has to be traced in the trace method. Tracing correctly is very important. If you get it wrong the garbage collector will reuse the memory and you get a use-after-free situation. Therefore, the clang plugin is a high priority for us to help developers avoid this pitfall.There are other classes of problems that you can get into. Using Persistent in something that is in the oilpan heap is disallowed (because it can lead to leaks - you should use Member and trace the pointer in the trace method). Again, this is something that can be detected with the clang plugin.Great. Is it reasonable to wait for the plugin to be ready before executing the bulk of the conversion?
To unsubscribe from this group and stop receiving emails from it, send an email to blink-dev+...@chromium.org.
Yes. It will take a bit of time to land the basic infrastructure for oilpan. While we are doing that we will also be working on the clang plugin so that we can get the verification in as we start doing the actual Blink changes that use Oilpan.
Could I ask why OwnPtr will also go away? imo, the reasons of Olipan is not related to removing OwnPtr: cycle reference, binding hack, read-after-delete, and etc..
In addition, removing OwnPtr may affect performance badly, because GC needs to trace the object as-is OwnPtr.
Can someone from V8 give some indication of when a v8::Value and internal field can point to an Oilpan heap object with the same semantics as a Member<T> and WeakMember<T> (and *not* Persistent<T>)?
Some of the benefits I imagined this having for the Custom Elements implementation rely on also simplifying cross Oilpan-V8 heap cycles. Is there a rough timetable for that?
Could I ask why OwnPtr will also go away? imo, the reasons of Olipan is not related to removing OwnPtr: cycle reference, binding hack, read-after-delete, and etc..In addition, removing OwnPtr may affect performance badly, because GC needs to trace the object as-is OwnPtr.
On the other hands, how about introducing some macro to remove boilerplate trace().class X : GarbageCollected<X> {...TRACABLE_MEMBER_BEGINFoo m_firstMember;Member<Bar> m_secondMember;TRACABLE_MEMBER_END...}INSTEAD OFvoid trace(Visitor* visitor){visitor->trace(m_firstMember);visitor->trace(m_secondMember);...}
Can someone from V8 give some indication of when a v8::Value and internal field can point to an Oilpan heap object with the same semantics as a Member<T> and WeakMember<T> (and *not* Persistent<T>)?Some of the benefits I imagined this having for the Custom Elements implementation rely on also simplifying cross Oilpan-V8 heap cycles. Is there a rough timetable for that?
Thanks for the detailed reply Mads.Re: moving reference cycles that Blink "exports" to the V8 heap back into the Blink side, was this feasible in practice? Because the pattern of references on the Blink side are basically controlled by people hacking Blink C++ code, but on the V8 side the page author can always create references between arbitrary JavaScript wrappers with expando properties.When the cycles and the author's ad-hoc references are on the V8 side, V8 GC can deal with them; if the cycles are on the Oilpan heap but the author's ad-hoc references remain on the V8 side, won't this create a cross-heap cycle and leak?Or are there existing cycles that never include an object that can have a JavaScript wrapper?
V8 Blink
AWrapper <---------------------> A
^ |
| v
BWrapper <---------------------> B
All pointers from Blink into the V8 heap are weak pointers and therefore the cross-heap cycles in these situations will not create leaks. If they did there would be a lot of leaks already because you can always make wrappers point to each other.
On Tue, Dec 10, 2013 at 10:03 AM, Dominic Cooney <domi...@chromium.org> wrote:
Thanks for the detailed reply Mads.Re: moving reference cycles that Blink "exports" to the V8 heap back into the Blink side, was this feasible in practice? Because the pattern of references on the Blink side are basically controlled by people hacking Blink C++ code, but on the V8 side the page author can always create references between arbitrary JavaScript wrappers with expando properties.When the cycles and the author's ad-hoc references are on the V8 side, V8 GC can deal with them; if the cycles are on the Oilpan heap but the author's ad-hoc references remain on the V8 side, won't this create a cross-heap cycle and leak?Or are there existing cycles that never include an object that can have a JavaScript wrapper?The situations I'm thinking of are situations where you would like to have a cycle on the Blink C++ side but instead of doing that you create the cycle through V8 instead. If you have two objects A and B that should keep each other alive but you cannot easily express that on the Blink side because of reference cycles, sometimes artificial wrappers are created and references put there instead. Then A and B will keep each other alive because A keeps B alive on the Blink side and if B is alive, then the wrapper for B keeps the wrapper for A alive and therefore A will be kept alive as well.V8 BlinkAWrapper <---------------------> A^ || vBWrapper <---------------------> BAll pointers from Blink into the V8 heap are weak pointers and therefore the cross-heap cycles in these situations will not create leaks. If they did there would be a lot of leaks already because you can always make wrappers point to each other. However, when the wrappers are no longer reachable from JavaScript the wrappers can go away and therefore they will no longer keep the Blink side objects alive.For these special situations, the pointer structure in V8 is only there to represent something that reference cycles makes hard to express in Blink and those we can move back into Blink.
Cheers, -- Mads
To unsubscribe from this group and stop receiving emails from it, send an email to blink-dev+unsubscribe@chromium.org.
The Wiki page you linked to says:Memory requests are satisfied by allocating portions from a large pool of memory called the heap. At any given time, some parts of the heap are in use, while some are "free" (unused) and thus available for future allocations.
That sounds like the same way we are using it. Could you elaborate on why you don't think we are using it the same way?
Mads, sorry if I missed it, but could you share links to the branch with good and bad examples of migration of some core Blink code. That would help assessing the changes and see how the code will look "after". (Why using artificial small examples if we have a luxury of taking a look into the future right away?)
Many parts of the plan resamble the humble attempt of inspector team to traverse Blink heap and collect native memory stats that happened half a year ago. That attempt proved that it was bloating code, making authors of incremental changes more involved with memory management, i.e. was adding a fair bit of complexity.
I do understand that it was primarily due to lack of awereness and I think you are doing a great job of educating people upfront. But it would be great to see whether in fact complexity raises and if yes, how much.
Regards
Pavel