Skip to content
STIMSMITH

ldst_index_trans

Technique WIKI v1 · 7/29/2026

ldst_index_trans() is a static helper in QEMU's RISC-V vector translation file (trans_rvv.c.inc) that translates indexed vector load/store instructions. The patch series 'trans_rvv.c.inc: write CSRs must call mark_vs_dirty() too' modifies ldst_index_trans() along with sibling helpers to unconditionally invoke mark_vs_dirty(), because vector stores modify the vstart CSR (a vector-state CSR) and therefore must transition mstatus.VS to Dirty per RISC-V Vector spec section 3.2.

ldst_index_trans

Overview

ldst_index_trans() is a translation-time helper for indexed RISC-V vector load/store instructions (the segment of the V extension whose addressing combines a base register rs1 with an index vector vs2). It lives in target/riscv/insn_trans/trans_rvv.c.inc, the QEMU TCG translation include that materializes every RISC-V Vector instruction, and is part of a family of helpers (ldst_us_trans, ldst_stride_trans, ldst_index_trans, ldst_whole_trans) that share the same overall structure for unit-stride, stride, indexed, and whole-register load/store variants.

Signature

From the diff in the patch evidence:

static bool ldst_index_trans(uint32_t vd, uint32_t rs1, uint32_t vs2,
                             uint32_t data, uint32_t is_store,
                             uint32_t is_load, uint32_t is_seg,
                             gen_helper_* fn, DisasContext *s)
{
    ...
    fn(dest, mask, base, index, tcg_env, desc);

    mark_vs_dirty(s);
    gen_set_label(over);
    return true;
}

The function takes the destination vector register vd, the scalar base rs1, the index vector vs2, the data size, the load/store/segment flags, the helper generator fn that emits the runtime helper call, and the disassembly context s.

Behavior

  1. Validates mstatus.VS against the current vector configuration and ensures vl/vtype/EEW are consistent. If the instruction is illegal in the current context, the helper jumps to the over exit label and returns true (the TB is finalized) without emitting any state-changing code.
  2. Loads rs1 into a base address and resolves the per-element vs2 index vector into index.
  3. Calls the generator function fn(dest, mask, base, index, tcg_env, desc) that will lower to a vext_ldst_* helper at runtime.
  4. Calls mark_vs_dirty(s) to record that vector state has changed.
  5. Jumps to gen_set_label(over) and returns true.

Why mark_vs_dirty() is called unconditionally

Before the fix, ldst_index_trans (and its three siblings) wrapped the mark_vs_dirty() call in if (!is_store), on the assumption that only the load path needed to mark vector state as Dirty. The patch [PATCH 1/3] trans_rvv.c.inc: write CSRs must call mark_vs_dirty() too by Daniel Henrique Barboza argues that this is wrong, citing RISC-V Vector specification section 3.2:

"When mstatus.VS is set to Initial or Clean, executing any instruction that changes vector state, including the vector CSRs, will change mstatus.VS to Dirty."

A vector store writes the vstart CSR to zero after execution (vext_ldst_us() in vector_helper.c), which is itself a vector-state CSR change. Therefore both loads and stores executed through ldst_index_trans alter vector state and must call mark_vs_dirty().

The fix removes the if (!is_store) guard:

-    if (!is_store) {
-        mark_vs_dirty(s);
-    }
-
+    mark_vs_dirty(s);
     gen_set_label(over);
     return true;

The same change is applied in ldst_us_trans, ldst_stride_trans, and ldst_whole_trans, dropping the file's net count by 15 lines (4 insertions, 15 deletions).

Related context

  • Fixed-by commit: 8e1ee1fb57 — "target/riscv: rvv-1.0: add translation-time vector context status", which originally introduced the guarded mark_vs_dirty() calls.
  • Patch author: Daniel Henrique Barboza <dbarboza@ventanamicro.com>, posted to the QEMU devel list on 2024-02-16.
  • Sibling helpers using the same pattern: ldst_us_trans, ldst_stride_trans, ldst_whole_trans.
  • Runtime counterpart: vext_ldst_us() in vector_helper.c, which is where env->vstart is reset to zero on store completion.

See also

CITATIONS

5 sources
5 citations
[1] ldst_index_trans() originally guarded its mark_vs_dirty() call with `if (!is_store)`, which the patch removes. [PATCH 1/3] trans_rvv.c.inc: write CSRs must call mark_vs_dirty() too
[2] Per RISC-V Vector spec section 3.2, executing any instruction that changes vector state, including vector CSRs, must transition mstatus.VS to Dirty. [PATCH 1/3] trans_rvv.c.inc: write CSRs must call mark_vs_dirty() too
[3] Vector stores executed through ldst_index_trans reset env->vstart to zero in vext_ldst_us() in vector_helper.c, which is a vector-state CSR change that requires mark_vs_dirty(). [PATCH 1/3] trans_rvv.c.inc: write CSRs must call mark_vs_dirty() too
[4] The fix is part of a patch series that makes the same unconditional mark_vs_dirty() change in ldst_us_trans, ldst_stride_trans, ldst_index_trans, and ldst_whole_trans in target/riscv/insn_trans/trans_rvv.c.inc (4 insertions, 15 deletions). [PATCH 1/3] trans_rvv.c.inc: write CSRs must call mark_vs_dirty() too
[5] The patch Fixes commit 8e1ee1fb57 ('target/riscv: rvv-1.0: add translation-time vector context status'), which introduced the original guarded mark_vs_dirty() calls. [PATCH 1/3] trans_rvv.c.inc: write CSRs must call mark_vs_dirty() too