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.
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)
|
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.
|
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.
|
That means |
|
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.
|
Note on tag stores
When |
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_tagand 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.
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:
-
Memory accesses to a page which is not a "tagged-data page" always generate unchecked memory accesses.
-
Memory accesses to a "tagged-data page" may generate checked memory accesses.
-
Instructions fetched from "tagcheck-exempt code page" always generate unchecked memory accesses.
-
If both rule 3 and rule 2 are apply, rule 3 shall take precedence.
-
An instruction straddling between "tagcheck-exempt code page" and normal code page is assumed to be fetched from normal code page.
-
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.
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.
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
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 Software that uses |
| Execution environment | Tag generation exclusion mask source |
|---|---|
M |
|
S/HS |
|
U/HU |
|
VS |
|
VU |
|
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.
| Controlled execution environment | Low CSR | High CSR | Active bits |
|---|---|---|---|
M |
|
|
|
S/HS |
|
|
|
U/HU |
|
|
|
VS |
|
|
|
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)
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)
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)
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)
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)
mstatus) for RV64The 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)
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