[llvm-dev] RFC: Strong GC References in LLVM

Sanjoy Das via llvm-dev
Mon, 11 Jul 2016 23:56:48 -0000

Hi Chandler,

Chandler Carruth wrote:
 > I think everything but this last weird aspect we already get from
 > address spaces.
 >
 > I misread the proposal originally and didn't understand that the
problem
 > was loading from an alloca *holding* the GC pointer, and thus it was a
 > normal and boring load that somehow has to have side-effects.
 >
 > I fundamentally think that we can't do that. I can see several ways to
 > make the result work without that.
 >
 > - Teach the statepoint rewriting to handle hoisted loads in some way
 > (haven't thought too much about how feasible this is)

I think that is impossible in general, given LLVM's IR.  If we start
with:

// IR_1
for (;;) {
   if (condition) {
     use(this->field);  // static type of(&(this->field)) == GCREF*
   }
}

and LICM it to:

// IR_2
GCREF val = this->field
for (;;) {
   if (condition) {
     use(val);
   }
}

then when rewriting it is incorrect to go from IR_2 to IR_1, assuming
that IR_2 is all we see, since in IR_2 use sees a single unique value
for every iteration whereas in IR_1 it may see different values in
different iterations.

 > - Tell LLVM that the load has this weird control dependence with some
 > mechanism (make it a special gc load intrinsic, or a volatile load,
or ....)

A gc_load intrinsic is not a reasonable idea actually, but I think it
will be worse in the long run.

To start off, we'd need to at least add gc_load to gc_load and store
to gc_load forwarding (the former for control equivalent gc_loads).
We have a set of analyses locally (downstream) that can infer Java
types, which can be used to justify hoisting gc_loads as well (by
proving that the gc_load will result in a GC reference in the new
location) that we'd like to eventually upstream.  Once all the dust
settles, I think we'll end up with too many cases of:

   if (auto *LI = dyn_cast<LoadInst>(V)) { process LI }
   if (auto *II = dyn_cast<IntrinsicInst>(V))
     if (II->getIntrinsicID() == gc_load) {
       prrocess II; // almost duplicate of process LI
     }

whereas a GCREF type (however it is represented, either as <ty>
addrspace(k)* or a new type) will let us fold most of the interesting
logic into the safety checks we already do for load instructions.

Slightly unrelated to the above, but here is another example (apart
from the LICM example above) of why doing "repair" in the rewrite pass
is difficult, after letting the optimizer run its course:

If we have:

   if (always_false) {
     GCREF val = this->field;
     use(val);
   }
   safepoint();
   if (always_false) {
     GCREF val = this->field;
     use(val);
   }
   clobber_this_field();

and we hoist and CSE this to:

   GCREF val = this->field;
   if (always_false) {
     use(val);
   }
   safepoint();
   clobber_this_field();
   if (always_false) {
     use(val);
   }

then we have "val" live over the safepoint(), but there is no
guarantee that "val" actually contains a GC reference.  Moreover, we
can't sink the load into the two blocks that use it, since we've
clobber_this_field() in a way that precludes that.

-- Sanjoy