[llvm-commits] [patch] add gcroot attribute, test

Gordon Henriksen gordonhenriksen at mac.com
Sat Sep 22 09:43:29 PDT 2007


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/llvm-commits@cs.uiuc.edu/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.


-------------- next part --------------
A non-text attachment was scrubbed...
Name: ShadowStackCollector.cpp
Type: application/octet-stream
Size: 16888 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/llvm-commits/attachments/20070922/a4b82c9d/attachment.obj>


More information about the llvm-commits mailing list