Memory tagging extension (Zimt)

The Zimt extension divides address space of the program in equal memory chunks of 16 bytes (hereafter mc). Each mc in address space can be associated with a tag (hereafter mc_tag) by software and mc_tag for mc is stored in memory defined by extension Svatag (see Virtually indexed tag storage (Svatag and Smvatag)).

Software (runtime memory allocator or compiler) that constructs pointers to access memory (and thus memory chunks) is expected to place a tag (hereafter pointer_tag) in high unused bits of the pointer as defined by the pointer masking extensions cite:[PMASKING]. Thus Zimt depends on the pointer masking extension and is applicable to only for XLEN=64. Pointer masking has to be enabled as a requirement for memory tagging to be enabled in the execution environment.

To maintain binary compatibility with implementations where memory tagging extension may not be implemented or may not be enabled for a workload, instruction encodings for memory tagging are taken from Zimop cite:[ZIMOP] encoding and thus make memory tagging extension dependent on Zimop.

A memory tag (mc_tag) can be up to 8 bits wide. Thus, depending on memory chunk counts, a load tag or store tag (see taxonomy for loads and stores) operation can be a misaligned access (e.g by performing a 2-byte tag access on an odd address boundary) and such operations must not raise misaligned exception. An implementation implementing Zicclsm extension may get this behavior implicitly.

The memory tagging extension is based on the Pointer Masking extension and follows the same rules for memory access. For further information, see section 24.2.6 of the privilege specification: Memory Accesses Subject to Pointer Masking.

Taxonomy for loads and stores

Zimt introduces new type of loads and stores to the hart and enforces tag checks on certain loads and stores. Following terminologies are defined for better clarity and will be used in the rest of the specification.

Regular load and regular store operations

Load and store operating on a page with permissions --R, X-R, -WR and XWR are termed as a regular load and regular store. Regular load and regular store memory operations are generated from an instruction (except settag and checktag) defined by ISA to perform memory accesses.

Checked load and store

Regular load and store which is subject to tag check is termed as checked load and store (or checked memory access)

Unchecked load and store

Regular load and store which is not subject to tag check is termed as unchecked load and store (unchecked memory access).

Load tag and store tag operations

load tag operation is a load of tag from tag storage as defined by Svatag or Smvatag extensions. store tag operation is a store of tag to tag storage as defined by Svatag or Smvatag extensions. load tag operation requires permission for tag storage memory to be --R while store tag operation require permission to be -WR.

Checked loads and stores shall generate load tag operation in order to fetch the tag for the memory chunk. settag instruction may generate a load tag followed by a store tag memory operation.

Tag widths

Width of the pointer_tag (hereafter pointer_tag_width) can be 4 bit or 7 bit wide depending on implementation. Discovery of pointer_tag_width is described in CSR bits for memory tagging section. Width of the mc_tag (hereafter mc_tag_width) is dependent on the pointer_tag_width. Following table describes valid configurations of pointer_tag_width and mc_tag_width.

Table 1. pointer_tag and mc_tag valid configurations
pointer_tag_width mc_tag_width

4

4

7

8

Tag placement in pointer

Zimt defines following instructions to annotate a pointer with pointer_tag and perform arithmetics on pointer_tag in a tagged pointer. Placement and arithmetics on pointer_tag are performed in-place in masked bits (as defined by pointer masking extension) starting at b63. Instructions introduced to place and perform arithmetics on pointer_tag are encoded using MOP.RR.1 from Zimop extension.

Existing RISC-V instructions can also be used to place (and do arithmetics) pointer_tag in masked bits of a pointer. But doing so increases code size, more so if tagging for stack memory is enabled. And new instructions help contain the code size growth.

Following are the instructions to place pointer_tag in the source register

Generate a tag - gentag rd

If memory tagging is enabled in the current execution environment (see Memory tagging enable and pointer_tag_width), hart clears rd, generates a pointer_tag value from the set of tag values permitted by the active tag generation exclusion mask, and places the result in rd[XLEN:XLEN-pointer_tag_width+1].

If memory tagging is disabled in the current execution environment (see Memory tagging enable and pointer_tag_width), then gentag instruction falls back to Zimop behavior and zeroes destination register.

A tag value is permitted if the corresponding bit in the active tag generation exclusion mask is clear. Even though the entropy space is small, the implementation must ensure that the generated tag is pseudorandom and uniformly distributed over the permitted tag values across invocations of gentag.

svg

See Appendix for example how this can be used by codegen.

Arithmetics on pointer tag - addtag rd, rs1, #tag_imm4

If memory tagging is enabled in the current execution environment (see Memory tagging enable and pointer_tag_width), addtag rd, rs1 instruction performs addition of pointer_tag specified in rs1[XLEN:XLEN-pointer_tag_width+1] with the unsigned value tag_imm4 shifted left by XLEN - pointer_tag_width bits and places incremented pointer_tag value in rd[XLEN:XLEN-pointer_tag_width+1].

If memory tagging is disabled in the current execution environment (see Memory tagging enable and pointer_tag_width), then addtag instruction moves rs1 into `rd.

svg

addtag, unlike the other instructions that are taken from Zimops codepoints, has three different semantics depending on execution environment

  • MT is not implemented: rd is zeroed;

  • MT is implemented but disabled in the current execution environment: rs1 is copied into rd;

  • MT is enabled in the current execution environment: the tag addition is performed.

That means rd always contains a valid pointer if MT is implemented, this allows improved codegen if the target is known to implement MT. See "Example: optimized stack tagging codegen" in Appendix.

addtag instruction can be used by the compiler for generating distinct pointer tags derived from a base tag (base tag obtained via gentag). Compiler can use this mechanism to assign different tags (with same base tag) for consecutive objects on stack and help catch adjacent overflow bugs. This also helps with language runtime during events like exception unwind to calculate tags for objects on stack in a deterministic manner.

See Appendix for example how this can be used by codegen.

set tag(s) for memory chunk(s)

Zimt defines an instruction to store tag (i.e. pointer_tag) value(s) for consecutive 1 to 16 memory chunk(s). Base address of the first memory chunk is calculated by doing rs1 & (~0xF). Count of memory chunk is encoded as 4 bit immediate (#chunk_count) in the instruction. This instruction is encoded using MOP.RR.0 from Zimop extension. Immediate encodings in #chunk_count are zero based and thus #chunk_count = 0 means first chunk and #chunk_count = 15 means 16th chunk.

If software desires to read tag in these non-performance critical paths, it can do so by creating an alternate read-only mapping of tag storage with the help of supervisor software.

Store tag(s) for memory chunk(s): settag rs1, #chunk_count

If memory tagging is enabled in the current execution environment (see Memory tagging enable and pointer_tag_width), settag instruction creates a mc_tag = rs1[XLEN:XLEN-pointer_tag_width+1] and generate a store tag memory operation with store data = mc_tag(s) for consecutive memory chunks encoded by chunk_count starting with the first memory chunk calculated from virtual address specified in rs1.

If memory tagging is disabled in the current execution environment (see Memory tagging enable and pointer_tag_width), then settag instruction falls back to Zimop behavior and zeroes x0, which is a no-op.

svg
Note on tag stores

When pointer_tag_width = 4 bit, mc_tag (stored) width is 4 bit and thus maximum width of tag store operation can be 64 bit wide (each memory chunk needs 4 bit tag and maximum possible chunks are 16. 4x16 = 64 bit). When pointer_tag_width = 7 bit, mc_tag (stored) width is 8 bit and thus maximum width of tag store operation can be 128 bit wide (each memory chunk needs 8 bit and maximum possible chunks are 16. 8x16 = 128 bit).

Depending on width of mc_tag, settag may end up being a read, modify and then write operation on the memory region defined by Svatag and Smvatag extensions. There are no atomicity requirements on the implementation for settag instruction. If atomicity is desired for store tag operation then it is software’s responsibility using existing mechanisms.

settag can generate store operations larger than maximum store width supported by implementation and implementation may choose to split it into multiple stores which follows hart’s memory consistency model. There are no ordering requirements or dependencies among splitted stores.

Implementation optimizations

Some software environments, such as M-mode firmware or tightly controlled embedded systems, may not need to preserve compatibility with implementations where memory tagging is not implemented or not enabled. In such environments, a combined operation that constructs a tagged pointer and stores the corresponding tag to tag storage may be desirable for common cases such as small stack allocations.

This extension does not define separate non-Zimop encodings for such operations. An implementation can instead recognize common instruction sequences that construct a tagged pointer and then store the corresponding tag, and can fuse them internally using microarchitectural fusion, provided that the architectural behavior remains equivalent to executing the original instruction sequence.

Memory ordering requirement

A regular memory access (regular load or regular store) to some virtual address va can not bypass the older store tag initiated by settag rs1=va.

This specification defines tag as the entity associated to virtual addresses. In case of aliasing (multiple virtual addresses map to same physical address), it is software’s responsibility to ensure that the tags are set according to software’s need for respective virtual address prior to memory accesses via aliased virtual address.

Exceptions

settag can raise store page fault or access fault depending on how tag storage is organized. If implementation doesn’t support misaligned accesses, settag instruction can raise misaligned exception if calculated address for locating tag is unaligned. Tag storage memory must be idempotent memory else settag raise store/AMO access-fault exception.

tag checks and privilege modes

If memory tagging is enabled for the execution environment, then memory tagging checks are performed by extracting pointer_tag from the input pointer and comparing pointer_tag against mc_tag associated with memory chunk for the respective address. If the effective address range of a load/store spans multiple memory chunks, the applicability of memory tag checks is determined separately for each memory chunk.

M-mode

If memory tagging is enabled in M-mode (see Memory tagging enable and pointer_tag_width), all regular loads and regular stores are subject to memory tagging checks.

Less than M-mode

If memory tagging is enabled in the current execution environment (see Memory tagging enable and pointer_tag_width) and satp.MODE == Bare, then all regular loads and regular stores are subject to tag checks.

If memory tagging is enabled in the current execution environment and satp.MODE != Bare, then a regular load and regular store is subject to tag checks only if the data page is a tagged-data page and load/store instruction is not on a tagcheck-exempt code page (see Tag checks on page basis).

tag checks

Once a regular load/store is determined (after paging bit checks and *envcfg control bit checks) to be subject to memory tagging checks, following further checks are performed

  • All stack pointer (sp/x2) relative accesses are not checked for tags (see notes).

  • Hart evaluates expression mc_tag == pointer_tag and if false then hart raises a software check exception with tval = 4.

While performing tag check on a regular load/store, fetching (load tag operation) mc_tag from the tag memory region holding tags may also result in a load page fault or load access fault and hart reports the virtual address of the tag in xtval.

As much as possible, compiler uses stack pointer (x2) to access stack objects local to a function. These accesses are deemed to be safe and thus are not subject to tag checks.

Explicit tag check

Zimt defines an explicit tag check instruction taken from MOP.RR.1 of Zimop extension, checktag rs1, #chunk_count. If memory tagging is disabled in the execution environment then checktag instruction reverts to Zimop behavior. If memory tagging is enabled in the execution environment then checktag instruction extracts pointer_tag from rs1 and performs load tag operation for memory chunks (count = #chunk_count) starting at virtual address rs1. If any of the loaded mc_tag value(s) mismatches with the pointer_tag specified in rs1, then software check exception is raised with tval = 4. If memory tagging is enabled in the execution environment then checktag instruction always makes the check irrespective of PTE.MTAG bit setting for the code page from where checktag instuction is fetched or for data page virtual address specified in rs1.

svg

Tag checks on page basis

Zimt introduces memory tag (MTAG) bit in first stage page table which defines "tagged-data page" and "tagcheck-exempt code page".

If paging is enabled, "tagged-data page" is a page translated using page tables with MTAG bit set and execute permission bit clear in first stage page tables. If paging is disabled and memory tagging is enabled for execution environment, then all data pages are "tagged-data page".

If paging is enabled, a "tagcheck-exempt code page" is a page translated using page tables with MTAG bit set and execute permission bit set in first stage page tables. If paging is disabled then "tagcheck-exempt code page" are not applicable and do not exist.

If memory tagging is enabled for the execution environment from *envcfg CSR, following rules apply for checked memory accesses:

  1. Memory accesses to a page which is not a "tagged-data page" always generate unchecked memory accesses.

  2. Memory accesses to a "tagged-data page" may generate checked memory accesses.

  3. Instructions fetched from "tagcheck-exempt code page" always generate unchecked memory accesses.

  4. If both rule 3 and rule 2 are apply, rule 3 shall take precedence.

  5. An instruction straddling between "tagcheck-exempt code page" and normal code page is assumed to be fetched from normal code page.

  6. A load/store straddling between "tagged-data page" and normal page is assumed as "tagged-data page" for both pages.

If memory tagging is not enabled for the execution environment via *envcfg CSR, then MTAG bit in page table entry is ignored by hart.

Tag checks on supervisor accesses to user pages

Supervisor accesses to user pages do not generate checked accesses and if memory tagging is enabled for user mode then MTAG bit in first stage page tables is ignored.

Memory tagging and hypervisor memory accesses

HLVX* instructions always generate unchecked loads.

If hstatus.MTAG_I is set, then HLV*/HSV* instructions do not generate checked accesses. If hstatus.MTAG_I is clear, then HLV*/HSV* instructions in HS and HU mode may generate checked accesses depending on effective privilege of VS or VU (as defined in privileged specification).

xMT_MODE (see Memory tagging enable and pointer_tag_width) for HLV* and HSV* instructions is defined based on the effective privilege of VS or VU (as defined in privileged specification), execution environment privilege (HS or HU) and may generate checked memory accesses (see below).

  • Execution environment HS/HU and effective privilege VS : henvcfg.MT_MODE

  • Execution environment HS and effective privilege VU : senvcfg.MT_MODE

  • Execution environment HU and effective privilege VU : hstatus.VUMT_MODE

If page based virtual memory is enabled for V=1, HLV*/HSV* generate checked memory accesses based on rules specified by paging bit MTAG in first stage page tables (see Tag checks on page basis).

If HLV*/HSV* instructions result in a tag mismatch, software check exception is delivered to HS mode with tval = 4 and hstatus.GVA set.

Memory tagging on loads/stores affected by Modify Privilege bit (MPRV)

If MPRV=1 and mstatus.MTAG_I is set, then load and store instructions generate unchecked accesses. If MPRV=1 and mstatus.MTAG_I is clear, then load and store instructions may generate checked accesses depending on effective privilege. xMT_MODE is selected based on effective privilege (see Memory tagging enable and pointer_tag_width).

If page based virtual memory is enabled for effective privilege and memory tagging is enabled for effective privilege, then checked accesses are generated based on rules specified by paging bit MTAG in first stage page tables (see Tag checks on page basis).

In case of a tag mismatch, software check exception is delivered to M mode with tval = 4.

CSR bits for memory tagging

In M-mode, enable for memory tagging is controlled via mseccfg CSR.

Enablement for privilege modes less than M-mode is controlled through xenvcfg CSR. Zimt adds two bits termed as MT_MODE to xenvcfg CSR which controls enabling of memory tagging and pointer_tag_width for the next privilege mode.

Memory tagging enable and pointer_tag_width

The term xMT_MODE for effective privilege is used to determine if memory tagging is enabled on memory accesses. In some cases like hypervisor load and store instructions (HLV*/HSV*), effective privilege will be different from underlying execution environment privilege. Following table determines source for xMT_MODE depending on effective privilege.

Table 2. xMT_MODE source
Privilege Memory tagging state

M

mseccfg.MT_MODE

S/HS

menvcfg.MT_MODE

U/HU

senvcfg.MT_MODE

VS

henvcfg.MT_MODE

VU

senvcfg.MT_MODE

VS(HLV*/HSV*)

henvcfg.MT_MODE

VU(HLV*/HSV* in HS)

senvcfg.MT_MODE

VU(HLV*/HSV* in HU)

hstatus.VUMT_MODE

Following table describes different encodings of MT_MODE and corresponding configuration

Table 3. MT_MODE encoding and its meaning
MT_MODE Memory tagging state

00

Disabled

01

Reserved

10

Enabled, pointer_tag_width = 4

11

Enabled, pointer_tag_width = 7

If memory tagging is implemented, implementation must implement pointer_tag_width = 4 at minimum. To discover maximum supported pointer_tag_width, software can write 0b11 to MT_MODE field in the xenvcfg CSR and read it back. If read back value is 0b11 then implementation supports both pointer_tag widths.

Tag generation exclusion mask

The active tag generation exclusion mask controls which pointer_tag values may be generated by gentag. The mask is 128 bits wide. Bit i corresponds to pointer_tag value i.

If bit i of the active tag generation exclusion mask is set, gentag must not generate pointer_tag value i. If bit i is clear, gentag may generate pointer_tag value i.

When pointer_tag_width = 4, only bits [15:0] of the mask are active. Bits [127:16] are ignored.

When pointer_tag_width = 7, bits [127:0] of the mask are active.

The tag generation exclusion mask affects only tag values generated by gentag. It does not affect addtag, settag, explicit tag checks, implicit tag checks, or tags constructed by other instruction sequences.

For the purposes of syntactic dependencies in the memory model, gentag has the active tag generation exclusion mask CSRs as source operands and rd as its destination operand.

The tag generation exclusion mask CSRs are WARL. A legal read-back value must leave at least one bit clear in bits [15:0]. The specific bit or bits left clear when legalizing an otherwise illegal write are implementation-defined. This constraint is independent of the currently active pointer_tag_width and is enforced when the exclusion mask CSRs are written; changing pointer_tag_width does not modify the exclusion mask CSRs. The reset value of the tag generation exclusion mask CSRs is zero.

Software can use this mask to prevent gentag from generating tag values with special software meaning, such as zero for untagged pointers or an all-ones pointer_tag value.

Software that uses addtag to derive additional tags from a base tag generated by gentag is responsible for ensuring that the derived tags have the desired software properties. For example, if software wants to avoid a set of tag values E not only for the base tag but also for tags derived by increments K, it should exclude from gentag any base tag b for which (b + k) mod (1 << pointer_tag_width) is in E for any k in K.

Table 4. gentag exclusion mask source
Execution environment Tag generation exclusion mask source

M

mmtagexclude1:mmtagexclude0

S/HS

menvmtagexclude1:menvmtagexclude0

U/HU

senvmtagexclude1:senvmtagexclude0

VS

henvmtagexclude1:henvmtagexclude0

VU

senvmtagexclude1:senvmtagexclude0

Tag generation exclusion mask CSRs

CSR addresses for these registers are to be allocated.

The Zimt extension adds pairs of 64-bit CSRs to hold the 128-bit tag generation exclusion mask for each controlled execution environment listed below. The low CSR contains mask bits [63:0]. The high CSR contains mask bits [127:64].

When pointer_tag_width = 7 is not supported by the implementation, bits [127:16] may be hardwired to zero.

When pointer_tag_width = 7 is supported by the implementation, bits [127:0] are writable subject to the WARL constraint above.

Only the bits corresponding to the active pointer_tag_width affect gentag. When the active pointer_tag_width = 4, bits [127:16] are ignored by gentag, even if they are writable.

Table 5. gentag exclusion mask CSRs
Controlled execution environment Low CSR High CSR Active bits

M

mmtagexclude0

mmtagexclude1

[15:0] or [127:0]

S/HS

menvmtagexclude0

menvmtagexclude1

[15:0] or [127:0]

U/HU

senvmtagexclude0

senvmtagexclude1

[15:0] or [127:0]

VS

henvmtagexclude0

henvmtagexclude1

[15:0] or [127:0]

Accessibility of Zimt CSRs

CSR access to senvmtagexclude0, senvmtagexclude1, henvmtagexclude0, and henvmtagexclude1 from modes less privileged than M causes illegal instruction exception if Smstateen is implemented and mstateen0.MT=0.

CSR access to senvmtagexclude0, and senvmtagexclude1 from VS mode causes virtual instruction exception if Ssstateen is implemented, hstateen0.MT=0, and mstateen0.MT=1 or Smstateen is not implemented.

Machine Security Configuration Register(mseccfg)

Machine security configuration register(`mseccfg`)
Figure 1. Machine security configuration register(mseccfg)

The Zimt extension adds the MT_MODE field (bit 35:34) to mseccfg. When the MT_MODE field is set to 0b10 or 0b11, memory tagging is enabled for M-mode.

When MT_MODE is 0b00, the following rules apply to M-mode:

  • Zimt instructions will revert to their behavior as defined by Zimop.

Machine Environment Configuration Register (menvcfg)

Machine environment configuration register (`menvcfg`)
Figure 2. Machine environment configuration register (menvcfg)

The Zimt extension adds the MT_MODE field (bit 35:34) to menvcfg. When the MT_MODE field is set to 0b10 or 0b11, memory tagging is enabled for HS/S-mode.

When MT_MODE is 0b00, the following rules apply to HS/S-mode:

  • Zimt instructions will revert to their behavior as defined by Zimop.

Supervisor Environment Configuration Register (senvcfg)

Supervisor environment configuration register (`senvcfg`)
Figure 3. Supervisor environment configuration register (senvcfg)

The Zimt extension adds the MT_MODE field (bit 35:34) to senvcfg. When the MT_MODE field is set to 0b10 or 0b11, memory tagging is enabled for VU/U-mode.

When MT_MODE is 0b00, the following rules apply to VU/U-mode:

  • Zimt instructions will revert to their behavior as defined by Zimop.

Hypervisor Environment Configuration Register (henvcfg)

Hypervisor environment configuration register (`henvcfg`)
Figure 4. Hypervisor environment configuration register (henvcfg)

The Zimt extension adds the MT_MODE field (bit 35:34) to henvcfg. When the MT_MODE field is set to 0b10 or 0b11, memory tagging is enabled for VS-mode.

When MT_MODE is 0b00, the following rules apply to VS-mode:

  • Zimt instructions will revert to their behavior as defined by Zimop.

Machine Status Register (mstatus)

Machine-mode status register (`mstatus`) for RV64
Figure 5. Machine-mode status register (mstatus) for RV64

The Zimt extension adds MTAG_I bit to mstatus. When a trap is taken to M-mode and mtinst is written with nonzero value, then MTAG bit for code page of trapping instruction is deposited in mstatus.MTAG_I.

Hypervisor Status Register (hstatus)

Hypervisor status register (`hstatus`)
Figure 6. Hypervisor status register (hstatus)

The Zimt extension adds VUMT_MODE (bit 51:50) to hstatus. VUMT_MODE selects the xMT_MODE if execution environment is HU mode and effective privilege mode is VU. hstatus.MTAG_I bit emulates MTAG bit for instruction fetch from code page. When a trap is taken to HS mode and htinst is written with nonzero value, then VS-stage page table’s MTAG bit on code page for trapping instruction is deposited in hstatus.MTAG_I.

Appendix

Example: stack tagging codegen

    function:
        # N.B. sp remains untagged at all times
        addi sp, sp, -512 # stack frame size of 512 bytes
        gentag t0   # generate a pointer_tag in high bits of t0
         :
        # first object is tagged <random tag> + 1
        addi s1, sp, 16
        addtag t1, t0, 1  # tag_imm4 = 1
        or s1, s1, t1
         :
        # second object is tagged <random tag> + 2
        addi s2, sp, 32
        addtag t1, t0, 2  # tag_imm4 = 2
        or s2, s2, t1
         :
        # scope of second object starts, tag
        settag s2, 1
        # [...] do things with s2 while in scope
        # scope of second object ends, tag back to zero
        addi s2, sp, 32
        settag s2, 1

Example: optimized stack tagging codegen

Software that targets implementations which support Zimt can optimize the stack codegen to skip or when constructing pointers to objects.

    function:
        # N.B. sp remains untagged at all times
        addi sp, sp, -512 # stack frame size of 512 bytes
        gentag t0   # generate a pointer_tag in high bits of t0
        or t0, sp, t0
         :
        # first object is tagged <random tag> + 1
        addi s1, t0, 16
        addtag s1, s1, 1  # tag_imm4 = 1
         :
        # second object is tagged <random tag> + 2
        addi s2, t0, 32
        addtag s2, s2, 2  # tag_imm4 = 2
         :
        # scope of second object starts, tag
        settag s2, 1
        # [...] do things with s2 while in scope
        # scope of second object ends, tag back to zero
        addi s2, sp, 32
        settag s2, 1