Issue 250408.1: Replace composite locations with mapping lists
| Author: | Ron Brender |
|---|---|
| Champion: | Ron Brender |
| Date submitted: | 2025-04-08 |
| Date revised: | |
| Date closed: | 2026-05-07 |
| Type: | Enhancement |
| Status: | Withdrawn |
| DWARF version: | 6 |
This is the original proposal. [ Return to the latest version ]
This is a concept proposal for what is intended to be a complete
replacement for the concept of composite locations as described in DWARF
V5 and as proposed in DWARF V6 (DW_OP_piece and friends are removed).
It also obviates the need for the yet-to-be proposed DW_OP_overlay
operation currently under discussion in the GPU group. Further, it
supersedes my earlier proposals 230712.2,
230712.3 and 230712.4 (it is actually inspired
by a combination of 230712.4 and the pending DW_OP_overlay
proposal). Finally, it appears to make DW_OP_lane unnecessary and removable.
In contrast to the formulations in V5 and proposed in V6, this approach does not notionally construct a complete description of an object in some imaginary address space. Rather it describes mappings only for locations of an object that are not in their “normal” locations when, due to optimization, part(s) or all of the object are promoted to alternate locations (typically registers). The intent is that any and all address arithmetic involved in accessing parts of an object are performed in their normal manner in the containing memory address space of an object but an access to the resulting location involves mapping that location as appropriate to its “current” promoted location prior to accessing any contents.
Briefly, the new operation DW_OP_map pops four operands from the stack and
pushes one result on the stack. The operands are:
-
A “source location” to be mapped to a new, or possibly the same, “target location”.
-
The beginning location of a sequence of contiguous “mapped locations” that are to be mapped.
-
The beginning location of a sequence of contiguous “target locations”.
-
The number of locations in each of the two sequences.
The result is defined as follows:
A. If the source location is not in the range of locations defined by the second and fourth operands, then the result is the same as the source location.
B. Otherwise, let N be the zero-based ordinal position of the source location in the mapped location sequence; the result is the corresponding Nth target location in the target location sequence.
The location information for an object is split into two parts:
-
The normal
DW_AT_locationinformation that gives the location of the object as a whole. If the complete object moves, but is always contained in a single contiguous sequence of locations, then it is completely describable using a familiar location list. This applies generally to all scalar objects. -
A new
DW_AT_mappingattribute is introduced whose value is also a location list. The “expressions” in this location list, however, consist solely of a sequence ofDW_OP_mapoperations together with the operations that push their second, third and fourth operations.
It is helpful to observe that the DW_AT_mapping attribute is only needed if and
when an object is not a single consecutive sequence of locations in memory.
To illustrate consider this simple example: Let OBJ be an object of type S which is
a struct or record consisting of two single precision floating point components, RE
and IM. Further suppose that in a certain part of the program, the second
component IM is promoted to register RX.
The value of the DW_AT_location attribute will be just the simple base location
for the object as a whole, &OBJ.
The value of the DW_AT_mapping attribute will be a location list in which the
location expression will look like:
! Assumes the source location to be mapped is already pushed
DW_OP_addrx (OBJ+4) ! Push beginning of O.IM
DW_OP_regRX ! Push beginning of target RX
DW_OP_const4 ! Size of each sequence
DW_OP_map
! Either the source location or loc(RX) pushed on stack
Note that a source location that is not in the same address space as the mapped locations is simply not mapped; it becomes the result.
The mapping is completely open-ended on both sides of the mapped sequence. This is deliberate and key to simple handling of hidden storage prior to the notional base address of an object, such as vtable pointers, RTTI or the like, that is seldom promoted (but if they are, they can be mapped like any other component). It also works well for C implicit arrays which have no defined length (other than that imposed by the size of the containing address space).
To find the location OBJ.RE, look up OBJ, get its base address from the
DW_AT_location attribute, look up component RE in its type, get its offset from
the DW_AT_data_member_location attribute and add them together—all exactly
as in classic DWARF. Then, push the result on the stack and invoke the appropriate
expression from the DW_AT_mapping list. In this example, the result
is the same as the input, namely the location of OBJ. For OBJ.IM, the same
process yields the location for register RX.
Now, extend this example by adding a third component to the struct/record: an
intensity value ITN, which is double precision floating point. During a PC range
where this component remains in memory, the example above continues to be
appropriate. At the point where the ITN component is moved into a register, say
RY, a new bounded location in the DW_AT_mapping list is needed that begins just
like the above but with the addition of
DW_OP_addrx (O+8) ! Push beginning of O.ITN
DW_OP_regRY
DW_OP_const8
DW_OP_map
Note that the result of the first DW_OP_map operation becomes the first parameter
of the second DW_OP_map operation.
If the earlier mapping “hits” and returns location register RX, that location will be
passed through as the final result. This makes the point that mapping regions for a
sequence of DW_OP_map operations should be disjoint. (The mechanism does not
enforce this, but a correct producer will do so.)
Aside: Here you see that the sequence consisting of the last three operands plus
DW_OP_map operation can be thought of as creating a mapping function which is
then applied to the first operand. This foursome, or a sequence of such foursomes,
is a good candidate for becoming a DWARF procedure that can be called from
many places. This can help reduce the size of expressions found in a location list.
Now consider how DW_OP_map can be used in a loop unrolling example. Using
some unexplained, handwavy notation and without getting buried in too much
detail, consider a bounded location expression appropriate in the body of a loop
which is unrolled by a factor two, with the promoted subvector promoted to, say,
R8 and R9. The vector VEC being processed is assumed to be a one-dimensional
array of run-time (non-constant) length. Let I be the loop control variable that
ranges from 0 to the upper bound.
The location expression for VEC withn the body is just the base address of the
vector as usual.
The expression in the DW_AT_mapping list entry looks like
! Location to be mapped already pushed
Push loc (&VEC[I])
Push loc (R8)
Push 4
DW_OP_map
Push loc (&VEC[I+1])
Push loc (R9)
Push 4
DW_OP_map
Recall that the loop control variable I will step through the loop using an increment
of 2 (the loop unrolling factor).
Nothing special is needed to handle a dynamic and unbounded size vector.
Further, it appears that DW_OP_lane may not be needed. Referencing VEC[x] in a
debugger will correctly access the needed lane, if appropriate, to the current value
of I.
There are several opportunities for additional operations that support the same paradigm but cater to common special cases and/or idiomatic patterns of use.
One obvious simple set of special cases is to define variants DW_OP_map1,
DW_OP_map2, DW_OP_map4, DW_OP_map8 and possibly DW_OP_map16,
corresponding to an implicit parameter of 1, 2, 4, 8 and 16, respectively, for the
size parameter.
I also think it would be simple to define special case unroll variants in support of typical unroll factors (say 2, 4, 8, 16). This would be especially true if the ABI defined some set of registers as a sequence. One might think of these as “macros” that are functionally equivalent to a sequence of mappings in the obvious way. I have not formalized the details but the approach seems promising.
A future option would be to make it possible to “call” a mapping list entry from another mapping list entry, without needing a carrier DIE. This would require a new kind of call operation but would allow significantly more space saving.
P.S. I am sure that an analogous bit-oriented version of this proposal (defining a
DW_OP_map_bit operation) can be readily formulated.