Hi Chris,

On Sep 21, 2007, at 13:24, Chris Lattner wrote:

On Sep 21, 2007, at 10:49, Gordon Henriksen wrote:

This is redundant; LLVM does it for you. See thread starting here:
  http://www.mail-archive.com/[email protected]/msg23987.html

I don't think so: the patch makes it so that llvm.gcroot *doesn't* implicitly null out the root. This means the front-end *does* need to explicitly emit the null init if it wants it, right?

Yes, you're right about the current behavior. At some point, I misread the 'shadowStackEntry.roots[num].root = NULL;' StoreInst in LowerGC.cpp as a redundant 'root = NULL;' on entry. Else I would've reserved this comment until after my patches were in.

ShadowStackCollector in my tree (attached) is somewhat different than LowerGC. It doesn't sacrifice portability, but reduces per-gcroot overhead to 1 store—or, better, zero overhead if an initializer is already present.

This compares to n+2 stores per gcroot for top-of-tree LowerGC. n is the number of times llvm.gcroot is dynamically executed. That could be commonly reduced to n+1, but that is still strictly worse than the worst-case of 1 for ShadowStackCollector.

The upshot is that the collector knows whether it is necessary to initialize roots or not, and does so on the program's behalf iff necessary.

I don't think the collector wants to be in the business of nulling out pointers.

Hm. I disagree. As I see it, using the back-end to emit initializers:

• is trivial
• makes front-ends and source programs marginally simpler, as I've already pointed out • reduces coupling between the front-end compiler and GC back-end (see below)
• better allows optimizations (see below)

By contrast, the only potential benefit I see to relying on front-end initialization is that it micro-optimizes LowerGC in some cases.[1] But this benefit is specious; the best way to optimize LowerGC is to replace it.

The important thing is that when the collector runs, it doesn't want a root with garbage in it.

Yes. And I'd argue that the collector back-end is best positioned to guarantee that.

Consider reference counting. Stores to the stack slot must be transformed as such:

  if (*slot != null) release(*slot);
  if (value != null) retain(value);
  *slot = value;

Slot initialization (or flow analysis potentially degrading to slot initialization) is always necessary to avoid 'release(garbage)' on first assignment. But initialization is not equivalent to 'store null -> slot', which would get expanded. Furthermore, there's no flow- sensitive way to differentiate store-as-an-initializer from store- with-reference-counting; llvm.gcroot could be in a loop.

Liveness-accurate stack maps have a similar characteristic, but in such a case, no initializer may be necessary at all.

However, if the source language requires null initialization semantics, then the front-end should emit explicit null initializers (as the variable comes into scope).

Even if the language doesn't require it, for GC roots, it seems very dangerous to hand the collector a root with random garbage in it, agree?

I don't, actually. gcroot doesn't get handed a value, but rather a variable. As you've point out, it's an annotation, not an instruction. The difference is quite significant for transformations.

Consider inlining if we rely on front-end initializers. The initializers have to be hoisted into the destination's entry block for a stack mapping collector.[2] Worse, the entry block may not dominate the live-in value, and null initializers may need to be *added* by the inliner. This is a mess—and the required and optimal behaviors are coupled to the collector. But with the back-end providing initialization, no changes are necessary to the inliner, and there is no coupling.

Consider also that dead store elimination applied to stack roots is liable to make erroneous transformations if we rely on front-end initialization. GC shouldn't logically inhibit DSE on stack roots.

Relying on front-end initialization seems to generally complicate or inhibit optimizations; back-end initialization seems to allow them to operate more or less freely.


I just don't see any upsides for front-end initialization.

Am I missing something?



The llvm.gcroot intrinsic can be declared to take any types you care to give it in the module, so long as its eventual type is like void(<ty>**, <ty2>*).

Actually no, I need to update the langref.html document to make this explicit, thanks for reminding me. The issue is that if you link two modules with different <ty>'s, the two different intrinsics would have to be merged somehow. By forcing the intrinsic to a specific type this problem doesn't happen.

I wondered about that. I'll have to fix several tests and the Verifier. Are these the blessed prototypes?

  void @llvm.gcroot(i8**, i8*)
  i8* @llvm.gcread(i8*, i8**)
  void @llvm.gcwrite(i8*, i8*, i8**)

— Gordon


[1] Using front-end initialization, LowerGC can skip the initializers for code like this:

  void foo(bool flag) {
    if (flag) {
      Object x, y, z;
      ...
    }
  }

[2] LowerGC doesn't pose any inlining problem, but that's due to the 'n' in its per-gcroot costs.


Attachment: ShadowStackCollector.cpp
Description: Binary data

_______________________________________________
llvm-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/llvm-commits

Reply via email to