thread-safe local static variable dynamic initialization

Jonathan Schilling jls at sco.com
Tue Jun 8 18:59:00 UTC 1999


> From: Christophe de Dinechin <ddd at cup.hp.com>
> 
> > However, it's not very effective to discuss the problem in the
> > abstract.  If someone has a specific proposal for solving the problem, 
> > submit it, and we can discuss concrete characteristics instead of
> > speculations.
> 
> Maybe we can present what HP aC++ does. Any other compiler has a  
> similar mechanism?

Here's what the SCO UnixWare 7 C++ compiler does for IA-32, from a (slightly
sanitized) design document.  It meets Jim's goal of having no overhead
for non-threaded programs and minimal overhead for threaded programs unless
actual contention occurs (infrequent), and meets Mike's goal of handling
exceptions in the initialization correctly (although it doesn't guarantee
that the thread getting the exception is the one that gets next crack
at initializing the static).  It's also worth noting that dynamic
initialization of local variables (static or otherwise) is very 
common in C++, since that's what most object constructions involve,
so I don't think this case is as rare as Jim does.

Jonathan Schilling		SCO, Inc.		jls at sco.com


   [...] This is in local static variables with dynamic
   initialization, where the compiler generates out a static one-time
   flag to guard the initialization. Two threads could read the flag as
   zero before either of them set it, resulting in multiple
   initializations.
   
   [...] Accordingly, when compilation is done with -Kthread on, a code
   sequence will be generated to lock this initialization.  
   [...] the basic idea is to have one guard saying
   whether the initialization is done (so that multiple initializations
   do not occur) and have another guard saying whether initialization is
   in progress (so that a second thread doesn't access what it thinks is
   an initialized value before the first thread has finished the
   initialization).  [...]
   
   When compiled with -Kthread, the generated code for a dynamic
   initialization of a local static variable will look like the
   following. guard is a local static boolean, initialized to zero,
   generated by the [middle pass of the compiler]. 
   Two bits of it are used: the low-order 'done bit'
   and the next-low-order 'busy bit'.
   
.again:
        movl    $guard,%eax
        testl   $1,(%eax)       // test the done bit
        jnz     .done           // if set, variable is initialized, done
        lock; btsl  $1,(%eax)   // test and set the busy bit
        jc      .busy
        < init code >           // not busy, do the initialization
        movl    $guard,%eax
        movl    $3,(%eax)       // set the done bit
        jmp     .done
.busy:
        pushl   %eax            // call RTS routine to wait, passing address
        call1   __static_init_wait      // of guard to monitor
        testl   %eax,%eax       // 1 means exception occurred in init code,
        popl    %ecx
        jnz     .again                  // start the whole thing over
.done                                   // 0 means wait finished

   The above code will work for position-independent code as well.
   
   The complication due to exceptions is: what happens if the
   initialization code throws an exception? The [compiler] EH tables will have
   set up a special region and flag in their region table to detect this
   situation, along with a pointer to the guard variable. Because the
   initialization never completed, when the RTS sees that it is cleaning
   up from such a region, it will reset the guard variable back to both
   zeroes. This will free up a busy-waiting thread, if any, or will reset
   everything for the next thread that calls the function.
   
   The idea of the __static_init_wait() RTS routine is to monitor the
   value of guard bits passed in, by looping on this decision table:
   
        done    busy
        0       0       return 1 in %eax        (EH wipe-out)
        1       1       return 0 in %eax        (no longer busy)
        0       1       continue to wait        (still busy)
        1       0       internal error, shouldn't happen

   As for how the wait is done [... not relevant for ABI, although currently
   we're using thr_yield(), which may or may not be right for this context].





More information about the cxx-abi-dev mailing list