The HLASM element of z/OS provides a print exit named ASMAXTXP,
which you can use in your assembly. It flags as errors things that
violate a transactional execution restriction, to the extent it can
determine those violations.
The user of transactions has control over such things as:
- The general register pairs that are to be saved at the initiation
of a transaction (TBEGIN or TBEGINC instruction) and restored upon
a transaction abort.
- Whether access register modification is allowed within the transaction.
Note: Upon transaction abort, access register values are never
restored.
- Whether floating point operations are allowed within the nonconstrained
transaction.
Note: Upon transaction abort, floating point
register values are never restored.
- Program interrupt filtering for a nonconstrained transaction.
For example, the application may ask that certain classes of program
interrupts be presented to the application as an abort, rather than
processed by the system as a program interrupt.
Consider the following simple transaction:
LA 2,Source_Data_Word
LA 3,Target_Data_Word
TBEGIN theTDB,X'8000' <<The "80" indicates to restore GRs 0-1 upon abort, each
bit in that byte corresponds to a double register pair.>>
BRC 7,Transaction_aborted
L 1,0(,2)
ST 1,0(,3)
TEND
<<When you get here, register 1 will have been changed by the "L", and the target
word will have been set.>>
...
Transaction_aborted DS 0H
<<When you get here, all the registers will have the value they had before the
TBEGIN instruction, the target word will be unchanged, and the TDB, identified on
the TBEGIN instruction, will contain information about the transaction abort.>>
You
can think of stores within a transaction as being "rolled back" upon
transaction abort. Similarly, you can think of the registers as being
saved at the transaction begin and then (optionally) rolled back to
their pre-transaction begin values.
When using nonconstrained transactions, the transaction must be
serialized against the fall-back path. For example, if one processor
is within a transaction and another within the fall-back path, each
needs to know enough to protect itself against the other. Also, typically,
a fall-back path needs to be serialized against other concurrent execution
of that fall-back path. You can think of the fall-back path as needing
"real" serialization (hardware or software-provided) and the transaction
needing to be able to tell that the fall-back path is running. One
way of accomplishing this is for the fall-back path to set a footprint
when it has obtained "real" serialization and for the transaction
to query that footprint. For example, consider a group of updates
that need to be done atomically:
Fall-Back Path
ENQ
Set bit I_Have_ENQ
the group of updates
Reset bit I_Have_ENQ
DEQ
Transaction
TBEGIN
BRC xx,Transaction_Aborted
If I_Have_ENQ is on then TABORT
the group of updates
TEND
Even this simple example is incomplete. To avoid cases
where a spin would result (when the ENQ holder does not get a chance
to reset the bit), the transaction path needs to limit the number
of times that the transaction is started over. This can be done using
a counter in a register that is not restored upon the abort or a non-transactional
store. Thus, at "Transaction_Aborted", there might be a test to see
if flow should proceed to the fall-back path or back to the TBEGIN.
Note: With this sort of approach, multiple transactional
users do not conflict with each other with respect to the I_Have_ENQ
bit (as, to them, it is just being read), but of course they do conflict
with each other with respect to the stores in the update group. A
fall-back path does conflict with the transaction such that its setting
of I_Have_ENQ will cause an in-process transaction to be aborted.