Message ID | 2808912.mi0B0sGudI@polaris |
---|---|
State | New |
Headers | show |
On Mon, Oct 15, 2012 at 7:45 PM, Eric Botcazou wrote: > This extends the lifetime of the hard register up to the beginning of the > function, causing reload to die on the complex division instruction. Does this still happen after my patch from yesterday to use DF_LIVE in IRA? > The attached patch prevents the invariant from being hoisted in this very > particular case. Any better idea? Maybe add a cost-free dependency on the clobber, so that it's moved with the insn? And/or maybe punt on all likely_spilled hard registers if -fira-loop-pressure is not in effect, it's unlikely to be a win in any case. Ciao! Steven
> Does this still happen after my patch from yesterday to use DF_LIVE in IRA? Yes, it even fails at -O2. > Maybe add a cost-free dependency on the clobber, so that it's moved > with the insn? Maybe. But I'm a little worried about (1) extending the lifetime of the hard register and (2) simply moving around a clobber of a hard register. > And/or maybe punt on all likely_spilled hard registers if > -fira-loop-pressure is not in effect, it's unlikely to be a win in any > case. I'd like to keep the change minimal, in particular restricted to uninitialized hard registers. The failure mode is very specific (and might be unique to the 64-bit ABI on Windows) so I'm not sure it's worth generalizing.
On Wed, Oct 17, 2012 at 11:55 AM, Eric Botcazou wrote: >> Maybe add a cost-free dependency on the clobber, so that it's moved >> with the insn? > > Maybe. But I'm a little worried about (1) extending the lifetime of the hard > register and (2) simply moving around a clobber of a hard register. AFAIU the clobber is there because it was emitted specially for this particular use of the hard register. So moving it around with the reason why it's there in the first place would make sense to me. I don't understand what you mean with extending the life of the hard register in this case. If you move the clobber with the instruction, the hard reg is dead before the clobber and after the insn that uses it, just like when the insn is not hoisted from the loop. So you don't extend the reg's live range if you move the clobber with it. And if the register is not dead after the insn, it must be live through the loop (otherwise it would be only partially available on the loop entry, coming in from the loop latch) so you don't extend the live range in that case either. (One thing I don't fully understand here, though, is why the clobber doesn't prevent the code motion to begin with. If you don't move the clobber, that could change the semantics of the program, right? E.g if you have before LIM something like this: while (some_condition) { clobber(ecx); invariant_use (ecx); ... }; use (ecx); and after LIM you have: invariant_use (ecx); while (some_condition) { clobber(ecx); ... }; use (ecx); that's not the same program anymore if some_condition isn't true... Not sure if that matters for this kind of uninitialized use.) > I'd like to keep the change minimal, in particular restricted to uninitialized > hard registers. The failure mode is very specific (and might be unique to the > 64-bit ABI on Windows) so I'm not sure it's worth generalizing. I've got a great dislike for special cases in the GCC code base, they tend to grow into common practice at alarmingly fast rates :-) But I suppose your point of view is more pragmatic in this peculiar case. Ciao! Steven
> I don't understand what you mean with extending the life of the hard > register in this case. If you move the clobber with the instruction, > the hard reg is dead before the clobber and after the insn that uses > it, just like when the insn is not hoisted from the loop. So you don't > extend the reg's live range if you move the clobber with it. And if > the register is not dead after the insn, it must be live through the > loop (otherwise it would be only partially available on the loop > entry, coming in from the loop latch) so you don't extend the live > range in that case either. In the former case, I agree that you don't really extend the live range. But you nevertheless extend its "span", i.e. you create a live range for the hard register out of the loop, while it's supposed to be used only just before the call in the argument preparation. In the latter case, I'm not sure that your conclusion holds: why can't it be used in a second insn and die there? > (One thing I don't fully understand here, though, is why the clobber > doesn't prevent the code motion to begin with. If you don't move the > clobber, that could change the semantics of the program, right? E.g if > you have before LIM something like this: > > while (some_condition) { clobber(ecx); invariant_use (ecx); ... }; > use (ecx); > > and after LIM you have: > > invariant_use (ecx); > while (some_condition) { clobber(ecx); ... }; > use (ecx); > > that's not the same program anymore if some_condition isn't true... > Not sure if that matters for this kind of uninitialized use.) I agree that moving an uninitialized use (of a hard register) without moving the associated clobber is weird. But I think most RTL passes don't care and this probably doesn't affect the correctness of the generated code. > I've got a great dislike for special cases in the GCC code base, they > tend to grow into common practice at alarmingly fast rates :-) > But I suppose your point of view is more pragmatic in this peculiar case. I'm just realistic: you don't want to make substantial changes in a powerful optimization pass just because of a corner case in an exotic language on a semi-exotic platform. :-)
Index: Makefile.in =================================================================== --- Makefile.in (revision 192447) +++ Makefile.in (working copy) @@ -3101,7 +3101,7 @@ loop-iv.o : loop-iv.c $(CONFIG_H) $(SYST intl.h $(DIAGNOSTIC_CORE_H) $(DF_H) $(HASHTAB_H) loop-invariant.o : loop-invariant.c $(CONFIG_H) $(SYSTEM_H) coretypes.h dumpfile.h \ $(RTL_H) $(BASIC_BLOCK_H) hard-reg-set.h $(CFGLOOP_H) $(EXPR_H) $(RECOG_H) \ - $(TM_H) $(TM_P_H) $(FUNCTION_H) $(FLAGS_H) $(DF_H) \ + $(TM_H) $(TM_P_H) $(FUNCTION_H) $(FLAGS_H) $(DF_H) $(TARGET_H) \ $(OBSTACK_H) $(HASHTAB_H) $(EXCEPT_H) $(PARAMS_H) $(REGS_H) ira.h cfgloopmanip.o : cfgloopmanip.c $(CONFIG_H) $(SYSTEM_H) $(RTL_H) \ $(BASIC_BLOCK_H) hard-reg-set.h $(CFGLOOP_H) \ Index: loop-invariant.c =================================================================== --- loop-invariant.c (revision 192447) +++ loop-invariant.c (working copy) @@ -47,6 +47,7 @@ along with GCC; see the file COPYING3. #include "cfgloop.h" #include "expr.h" #include "recog.h" +#include "target.h" #include "function.h" #include "flags.h" #include "df.h" @@ -784,7 +785,22 @@ check_dependency (basic_block bb, df_ref defs = DF_REF_CHAIN (use); if (!defs) - return true; + { + unsigned int regno = DF_REF_REGNO (use); + + /* If this is the use of an uninitialized argument register that is + likely to be spilled, do not move it lest this might extend its + lifetime and cause reload to die. This can occur for a call to + a function taking complex number arguments and moving the insns + preparing the arguments without moving the call itself wouldn't + gain much in practice. */ + if ((DF_REF_FLAGS (use) & DF_HARD_REG_LIVE) + && FUNCTION_ARG_REGNO_P (regno) + && targetm.class_likely_spilled_p (REGNO_REG_CLASS (regno))) + return false; + + return true; + } if (defs->next) return false;