Closed Bug 393034 Opened 17 years ago Closed 17 years ago

Allocate DOM objects using MMgc

Categories

(Core :: DOM: Core & HTML, enhancement)

Other Branch
enhancement
Not set
normal

Tracking

()

RESOLVED INCOMPLETE

People

(Reporter: jorendorff, Unassigned)

References

Details

DOM currently uses global |operator new| and XPCOM reference-counting.  This change would touch:  cycle collector, XPConnect, and some AddRef/Release methods.

This might benefit from some discussion of the future of XPCOM.  I'll open that discussion on mozilla.dev.tech.xpcom, the week of 27 August 2007.  I don't anticipate too much controversy over this change though.

The long-term goal here should be to eliminate the cycle collector.  Instead of adding a few lines of code to become a cycle collection participant, a class could allocate from MMgc and get comparable advantages, plus (I think) a performance boost.

To get the full simplicity benefit, all GC-managed memory needs to be allocated from the same MMgc::GC.  This probably means a global singleton GC (just as Gecko has a global singleton JSRuntime).

Where to draw the line (i.e. which objects count as "DOM objects") is an open question.
Don't forgot those DOM peers and owner...

/be
Until we turn on incremental GC, we don't need to use the write-barrier macros and helper classes for GCObjects. But for RCObjects IIRC, we do need the write barrier or we lose DRC. So we'll need write barriers in the DOM code.

See http://wiki.mozilla.org/Tamarin::MMgc for more on DRC.

The global singleton GC can be the XPConnect JSRuntime's GC. We'll still have XPConnect in the picture for this stage, including all the wrapped natives and scriptable helpers and security manager (caps) indirected checks. This stage is about unifying memory management and eliminating cycle collector usage.

/be
DRC is a big improvement when most nodes in the graph are *not* in cycles.

If a node is in a cycle, it will never be added to the ZCT.  It will behave like an ordinary GC-managed object (except with some refcounting overhead).  All DOM nodes are always in cycles, so I think plain old GC is probably best.  Might be missing something.

The reason I think we want DRC (RC, really) is for prompt finalization. The Gecko DOM does not reflect into JS eagerly, and with XPCOM refcounting most of the native node and element data structures go away on a last release. Waiting for GC will increase RSS and probably hurt perf as well as footprint.

I also have an idea for making DRC work when writing a heap to heap reference, so long as both objects are in the ZCT (and therefore the referee is itself referenced only by zero-count objects and/or the stack). This would help temporary DOM creation: building a new tree and preparing to insert it into a (non-zero-RC) window-connected DOM. The write barrier would have to trace the zero-count graph of objects, of course, but this would be fast single-threaded code. This is for another bug, but I wanted to mention it here too.

/be
Aha, I didn't realize DOM used weakrefs like that.  bz straightened me out. 
Time to read up on MMgc WeakRefs.
> The Gecko DOM does not reflect into JS eagerly

While true for most of the DOM, window.document _is_ effectively reflected eagerly.  Certainly in Firefox, at least for non-subframe documents, since the chrome accesses it.  Since documents own the DOM (even with the nsDocument::Destroy mess we haven't nixed yet, they keep owning it), we don't really destroy the DOM until the document gets GCed.
(In reply to comment #6)
> > The Gecko DOM does not reflect into JS eagerly
> 
> While true for most of the DOM, window.document _is_ effectively reflected
> eagerly.  Certainly in Firefox, at least for non-subframe documents, since the
> chrome accesses it.  Since documents own the DOM (even with the
> nsDocument::Destroy mess we haven't nixed yet, they keep owning it), we don't
> really destroy the DOM until the document gets GCed.

In that case, forget RCFinalizedObject, let's derive DOM objects from GCFinalizedObject. So I'm wrong in general, and the few cases of newborn DOM object graphs being built but not owned by a document is probably too rare to care about prompt finalization via RC.

Jason, I don't think weak refs matter, but I missed whatever bz said to you -- could you clarify?

Whether we want to switch all XPCOM objects to MMgc in order to do this is (in my mind at any rate) an open question.

/be
(In reply to comment #7)
> Jason, I don't think weak refs matter, but I missed whatever bz said to you --
> could you clarify?

Earlier I said "All DOM nodes are always in cycles".  bz explained to me that not all pointers in the DOM are hard references.  The way he described it, it sounds like parentNode is a weak reference.  It doesn't AddRef the parent, and it's automatically nulled out if the parent goes away.

Should we preserve this?  It should be a rare case.  I vote no.
> Should we preserve this?

The behavior is there to avoid leaks.  We don't need it for its own sake (and in fact would be happy to get rid of it).
Agreed. That's more of a effect of using reference counting in a tree structure w/o a reliable place to tear stuff down etc. In a GC world all those pointers can simply be "strong" references.
mrbkap gave me another XPConnect lesson yesterday.  Of particular interest: nsXPCWrappedJS already does one of the more interesting things we need, namely make a GC-managed object support AddRef and Release according to the nsISupports contract.  (The first AddRef() causes the JS object to become rooted; the last Release() un-roots it.)

Possible approach:  Maybe there should be a base class (maybe in xpcom/glue?) that (a) derives from both MMgc::GCFinalizedObject and nsISupports; (b) provides correct implementations of AddRef() and Release() along the lines of what nsXPCWrappedJS already does.  DOM nodes could derive from that.
Depends on: 388070
Wouldn't they need to be GC roots as well? Tons of code, outside of the DOM etc can hold on to XPCWrapperJS objects, and until all that code lives in MMgc's memory we need a way for MMgc to know about the references so they don't go away. Right?
Johnny pointed out that the refcnt in RCObject etc. is advisory in this sense: if it's non-zero but MMgc can't reach the object, it will collect it; if it reaches 0 after being non-zero, then the object can be promptly finalized (if it's an RCFinalizedObject) and collected.

Here's a list of simple things that might possibly work:

* Convert XPConnect and the DOM to all derive from nsISupportsGC, which adds QI to GCFinalizedObject. That adds no net vptr per instance, and it removes mRefCnt. Bonus points for removing AddRef and Release.

* Make sure our rooting story is as solid with MMgc as with SpiderMonkey. This should not be hard since MMgc is conservative.

* Make sure any lifetime managed solely by mRefCnt, not by JS GC connectivity, is now managed by GC connectivity. Boris's point in comment 6 gives me hope. Where a DOM native is reflected into JS but not part of a document, we will want the DOM native's lifetime to be governed by JS and the native code that uses that object. So we may need a root or two.

* Try to eliminate XPConnect from the DOM as soon as we have Tamarin integration (a later stage, but I wanted to mention it here). This means generating AS3 (er, ES4/JS2) or ABC (binary) glue for Tamarin from our IDL, and generating C++ headers for our native code to use. Worth some exploration now, as Mark Hammond is already doing this for native ActiveScript/COM integration in ScreamingMonkey.

/be
(In reply to comment #12)
> Wouldn't they need to be GC roots as well? Tons of code, outside of the DOM etc
> can hold on to XPCWrapperJS objects, and until all that code lives in MMgc's
> memory we need a way for MMgc to know about the references so they don't go
> away. Right?

Ack, I'm not communicating.  Let me step through the reasoning that led up to comment 11.  It should be logged here anyway.

I believe the entire codebase relies implicitly on assumptions like these:  (1) all XPCOM objects implement nsISupports; (2) all C++ objects reflected into JS are XPCOM objects; (3) all XPCOM interfaces derive from nsISupports; (4) hard XPCOM references keep an object alive.  We're up to our neck in these assumptions.

(Aside:  I think ultimately we can move all XPCOM over to GC, eliminating AddRef and Release, without breaking these assumptions.  Long-term.)

OK.  So we want to allocate DOM objects using MMgc.  It seemed to me at first that the approaches fell into two categories:  (A) eliminate AddRef and Release from the entire universe and use MMgc for everything; (B) eliminate AddRef and Release only from DOM objects.

I'm all for (A), but it is a long-term project.  MMgc isn't thread-safe yet, for one thing.

But (B) is even harder.  Do we remove nsISupports from all DOM objects?  Then we've broken assumptions (1) (2) and (3).  Do we allow DOM objects to continue to implement nsISupports, but with AddRef and Release as no-ops?  Then we've broken assumption (4).

Maybe we can fix that.  What if MMgc could find all hard XPCOM references to GC-managed objects?  Then it wouldn't matter if AddRef is a no-op.  Assumption (4) would be restored.  All we have to do is hack nsCOMPtr (and nsRefPtr, and nsSupportsArray, and...) so that every time it AddRefs an object, it checks to see if the object is a GC-managed object (if you're unsure, perhaps because the static type of the pointer is nsISupports*, you can just ask MMgc) and, if so, made the nsCOMPtr (or nsRefPtr or nsSupportsArray element, or whatever) into a root.

That seemed just a touch insane, and then I realized there was a much better approach:  (C) continue to support AddRef and Release.  But don't call them from DOM.  DOM itself will use ordinary pointers, but other XPCOM code that happens to get a reference to a DOM object will see a fully functional, non-abstraction-breaking XPCOM object.

Here's how it would work.  GC-managed XPCOM objects would still have refcounts.  If a GC-managed object happens to be created and used without ever passing into XPCOM-land, great.  AddRef is never called, so there's no need to root anything.  It works just like MMgc is intended to work.  But suppose a GC-managed DOM object is passed to XPCOM code.  On the first AddRef call, the object adds itself to a global data structure that is part of an MMgc root.  On the last Release, the object removes itself from that data structure.

(I was thinking one big global hash-set of pointers.  A doubly-linked list is faster but takes more space.)

I was really happy about this, so I sent mrbkap email about it, and the reply came: "Y'know, this sounds a lot like what we do for XPCWrappedJSs already. See nsXPCWrappedJS::AddRef in js/src/xpconnect/src/xpcwrappedjs.cpp."

http://lxr.mozilla.org/seamonkey/source/js/src/xpconnect/src/xpcwrappedjs.cpp#192

XPCWrappedJS is the existing boundary between the refcounting world and the GC world.  By moving DOM into the GC world, we're moving the boundary.  So it makes sense for the techniques used in XPCWrappedJS to be deployed in new places.
Sounds like progress has been made via newsgroups, IRC, and wiki.mozilla.org (http://wiki.mozilla.org/XPCOMGC). What's the plan for this bug, if different from the plan recorded there?

/be
No longer blocks: 393023
I believe that we should go ahead and do "Allocate XPCOM objects using MMgc". There are some particular problems that need to be solved, and we're still at least 4-6 weeks away from some of the automated rewriting (Taras is on vacation for a couple weeks and needs to do some Elsa hacking). I may be able to shortcut some of that work, at least for the experimentation phase.
Going to resolve this INCOMPLETE in favor of the XPCOMGC work, which has repos and wikis but no tracking bugs (yet).
Status: NEW → RESOLVED
Closed: 17 years ago
Resolution: --- → INCOMPLETE
Component: DOM → DOM: Core & HTML
You need to log in before you can comment on or make changes to this bug.