Instead of instrumenting thread private memory, we save the
addresses in a log which we later use to save/restore the addresses
upon transaction start/restart.
The log is keyed by address, where each element contains individual
statements among different code paths that perform the store.
This log is later used to generate either plain save/restore of the
addresses upon transaction start/restart, or calls to the ITM_L*
logging functions.
So for something like:
struct large { int x[1000]; };
struct large lala = { 0 };
__transaction {
lala.x[i] = 123;
...
}
We can either save/restore:
lala = { 0 };
trxn = _ITM_startTransaction ();
if (trxn & a_saveLiveVariables)
tmp_lala1 = lala.x[i];
else if (a & a_restoreLiveVariables)
lala.x[i] = tmp_lala1;
or use the logging functions:
lala = { 0 };
trxn = _ITM_startTransaction ();
_ITM_LU4 (&lala.x[i]);
Obviously, if we use _ITM_L* to log, we prefer to call _ITM_L* as
far up the dominator tree to shadow all of the writes to a given
location (thus reducing the total number of logging calls), but not
so high as to be called on a path that does not perform a
write.
One individual log entry. We may have multiple statements for the
same location if neither dominate each other (on different
execution paths).