r230255 - Only lower __builtin_setjmp / __builtin_longjmp to

Hal Finkel hfinkel at anl.gov
Tue Mar 3 14:39:08 PST 2015


----- Original Message -----
> From: "Joerg Sonnenberger" <joerg at britannica.bec.de>
> To: cfe-commits at cs.uiuc.edu
> Sent: Tuesday, March 3, 2015 2:51:59 PM
> Subject: Re: r230255 - Only lower __builtin_setjmp / __builtin_longjmp to
> 
> On Tue, Mar 03, 2015 at 01:00:30PM -0600, Hal Finkel wrote:
> > > Huh? How do you know that the intermediate functions haven't
> > > clobbered a
> > > register? Without unwinding, which we explicitly do not want to
> > > do
> > > here,
> > > you can't. As such you *can't* avoid the spilling.
> > 
> > You can because, for a caller-saved register, the caller saved them
> > (if
> > it, indeed, needed to do so). That's the nice thing about the
> > builtins:
> > they're call-site specific. So when you call setjmp, you don't need
> > to
> > save them if you don't need them afterward. When the caller of
> > setjmp
> > returns, its caller will restore those registers as needed (as it
> > would
> > have anyway). You do need to save callee-saved registers (along
> > with
> > any other registers the function is actually using).
> 
> Again, the intermediate functions are interrupted. They can not
> restore
> whatever they spilled on the stack.

Again, your statement is correct, but not entirely relevant. However, the example I included was not ideal, let me construct another one more-relevant to your point. Again, we have a register, v1, which is caller saved. However, a caller only needs to have a caller-saved register if it cares about the value in the register (otherwise, it just gets clobbered, and that's fine).

void work(jmp_buf &jb) {
  ...
  if (...) {
    // Here we just load a few reserved registers and jump.
    __builtin_longjmp(&jb, 1);
  }

  // control flow may or may not reach here.
}

foo() {
  // This function does not use v1, its caller may have saved it if it cares.
  jmp_buf jb;

  ...

  // We don't use v1 here, but it is caller saved, so we're not responsible for preserving it, and don't save it here.
  if (__builtin_setjmp(&jb)) {
    // We don't restore v1 here (we didn't save it); also, since we're just returning, we only need to reload callee-saved registers here.
    return 1;
  }
  // We don't restore v1 here either (again, we didn't save it - we didn't need to)

  work(jb);

  // The control flow may or may not reach here, but regardless, we don't restore v1 because it is caller-saved.
  return 0;
}

So, yes, work() is interrupted, so its callee-saved register reload instructions will not execute, but that's not relevant. That is not what is restoring v1 if necessary, that happens in foo()'s caller, if necessary, which is higher up in the call stack than the setjmp.

 -Hal

> You need to save them all unless
> you
> can use CFG or unwind data for doing it more selective. The latter is
> not really sj/lj anymore and tends to perform horrible on many
> platforms
> due to the overhead associated with stack unwinding. The former is
> only
> possible in a limited set and I don't think we do that. For all other
> registers, they are free game. Good libc implementations exploit
> that.
> I'm ignoring the mess with signal mask saving, which is irrelevant
> here.
> The only difference for the place using setjmp is if you can merge
> the
> the register saving with the function prologue spilling. As I said,
> functions using setjmp tend to be simple, the requirement for using
> volatile for local variables is a good enough incentive to avoid more
> complex things. So unless your architecture is extremely register
> starved, it would likely not touch any callee-safe registers at all.
> So you can't really gain much on the setjmp side. Of course, there
> are
> old setjmp implementations that are brain dead and will save all
> registers. But that's like comparing qsort to bogosort and not to
> heapsort.
> 
> > I think it is also worth noting that, as we implement them, the job
> > of
> > restoring registers is shifted compared to the library calls. So,
> > when
> > using library setjmp/longjmp, setjmp saves all of the necessary
> > state
> > into the jump buffer, and longjmp restores all necessary state and
> > jumps to the designated location. With the builtins,
> > __builtin_setjmp
> > itself saves very little state into the jump buffer (only the
> > address
> > and some reserved registers), but causes the function calling it to
> > spill and restore only necessary state around it. __builtin_longjmp
> > restores only the reserved registers necessary to make the jump,
> > nothing more (the spill/restore code around the __builtin_setjmp
> > takes
> > care of the rest).
> > 
> > So, for example, if we have some register, v1, which is caller
> > saved...
> > 
> > void bar(jmp_buf &jb) {
> >   // v1 is not used in this function, and the caller saved it if
> >   necessary
> >   // so v1 is not spilled here
> >   __builtin_setjmp(&jb);
> >   // and v1 is not restored here (the caller saved it if necessary,
> >   and will restore it if necessary, when we return)
> > }
> 
> If v1 is not callee-safe, a setjmp/longjmp pair doesn't have to
> preserve. There is nothing to be gained.
> 
> > 
> > foo() {
> >   jmp_buf jb;
> >   // v1 is saved here if necessary
> >   bar(jb);
> >   ...
> >   // the value of v1 saved above is loaded here somewhere
> >   ...
> >   __builtin_longjmp() // this does not explicitly restore v1 here,
> >   although a library call would need to
> > }
> 
> See above, that's wrong. There is one constellation where
> __builtin_setjmp can be effectively free: if it called more than once
> in
> the same function. I would be surprised if that happens regulary
> outside
> EH...
> 
> Joerg
> _______________________________________________
> cfe-commits mailing list
> cfe-commits at cs.uiuc.edu
> http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits
> 

-- 
Hal Finkel
Assistant Computational Scientist
Leadership Computing Facility
Argonne National Laboratory



More information about the cfe-commits mailing list