Page MenuHomePhabricator

Code generation support for lvalue bit-field conditionals.
Needs ReviewPublic

Authored by rsmith on Aug 11 2020, 12:32 AM.

Details

Summary

C++ permits use of (cond ? x.a : y.b) as an lvalue even when a and/or b is a
bit-field. We previously failed to generate IR for such constructs.

Handle these cases with a new form of LValue representing a choice between two
other LValues. Whenever we want to generate a primitive operation on a
conditional lvalue, generate a branch to perform the operation.

This would be sufficient to also support lvalue conditionals between other
kinds of non-simple lvalue, such as a conditional between the imaginary
component of a _Complex T and a vector element, but Sema rejects all the other
combinations for the moment.

One case that's not yet supported is atomic compare-exchange operations on
bitfield conditional lvalues. These only arise through #pragma omp atomic
constructs, and would probably not be too hard to support, but we need to teach
the OpenMP code to cope with its subexpression being emitted multiple times.

Diff Detail

Event Timeline

rsmith created this revision.Aug 11 2020, 12:32 AM
Herald added a project: Restricted Project. · View Herald TranscriptAug 11 2020, 12:32 AM
rsmith requested review of this revision.Aug 11 2020, 12:32 AM
rsmith updated this revision to Diff 284594.Aug 11 2020, 12:38 AM
  • Remove development aid from test.

I always imagined that we'd implement this by just inverting the emission: emit the RHS of the assignment and then pass a callback down to a function that descended into conditional operators and called the callback when it hit a leaf. Having an LValue case that's an arbitrarily-nested conditional seems unfortunate.

I always imagined that we'd implement this by just inverting the emission: emit the RHS of the assignment and then pass a callback down to a function that descended into conditional operators and called the callback when it hit a leaf. Having an LValue case that's an arbitrarily-nested conditional seems unfortunate.

I thought I'd convinced myself that that approach couldn't work, but I'm no longer so convinced. (I had thought there were cases where we would need to run arbitrary code between creating the lvalue and disposing of it, and that arbitrary code could be something we were only able to emit once for whatever reason.) The fact that assignment and compound-assignment operators all evaluate their RHS before their LHS (in the language modes where they're sequenced) helps a lot; expressions such as ((cond ? a.x : a.y) += f()) *= g() still seem like they could compositionally work with this model, and even things like (cond1 ? b : (cond2 ? a.x : a.y) += f()) += g(). Hm, but what about int n = (cond ? a.x : a.y) += 1;? I guess we'd need to carry the intent to perform a load of the conditional lvalue into the callback too.

OK, I think that all works, but I'm a lot less confident of the absence of surprising corners that would defeat that strategy than with the approach of this patch -- the correctness of doing this with a single branch seems to hinge crucially on subtle program structure restrictions, in particular the presumptive fact that we never need control flow to converge between creating the lvalue and consuming it. On the other hand, it would also let us emit better IR for things like (a += 1) *= 2 (removing the intermediate store to a) if we wanted, and maybe it's a good strategy to pursue for that reason?

I can give that a go next time I find some cycles to work on this.