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:

  1. A “source location” to be mapped to a new, or possibly the same, “target location”.

  2. The beginning location of a sequence of contiguous “mapped locations” that are to be mapped.

  3. The beginning location of a sequence of contiguous “target locations”.

  4. 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:

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.