Register Management

Relevant source files

This document covers the register data structures and management systems that maintain CPU state for virtual machines in the RISC-V hypervisor. This includes general-purpose registers, control and status registers (CSRs), and the mechanisms for saving, restoring, and accessing register state during VM execution and context switches.

For information about trap handling that uses these registers, see Low-Level Implementation. For details about the VCPU implementation that manages these registers, see Core VCPU Implementation.

Register Data Structures

The register management system is built around a hierarchy of data structures that organize different categories of CPU state. The main container is VmCpuRegisters, which holds all register state needed for virtualization.

Core Register Structure Hierarchy

flowchart TD
VmCpuRegisters["VmCpuRegistersMain register container"]
HypervisorCpuState["HypervisorCpuStatehyp_regs"]
GuestCpuState["GuestCpuStateguest_regs"]
GuestVsCsrs["GuestVsCsrsvs_csrs"]
GuestVirtualHsCsrs["GuestVirtualHsCsrsvirtual_hs_csrs"]
VmCpuTrapState["VmCpuTrapStatetrap_csrs"]
HypGprs["GeneralPurposeRegistersgprs"]
HypCsrs["CSRs:sstatus, hstatusscounteren, stvec, sscratch"]
GuestGprs["GeneralPurposeRegistersgprs"]
GuestCsrs["CSRs:sstatus, hstatusscounteren, sepc"]
VsCsrList["VS-level CSRs:htimedelta, vsstatus, vsievstvec, vsscratch, vsepcvscause, vstval, vsatp, vstimecmp"]
VirtHsCsrList["Virtual HS CSRs:hie, hgeie, hgatp"]
TrapCsrList["Trap CSRs:scause, stvalhtval, htinst"]
GprIndex["GprIndex enumZero, RA, SP, GP, TPT0-T6, S0-S11, A0-A7"]

GuestCpuState --> GuestCsrs
GuestCpuState --> GuestGprs
GuestGprs --> GprIndex
GuestVirtualHsCsrs --> VirtHsCsrList
GuestVsCsrs --> VsCsrList
HypGprs --> GprIndex
HypervisorCpuState --> HypCsrs
HypervisorCpuState --> HypGprs
VmCpuRegisters --> GuestCpuState
VmCpuRegisters --> GuestVirtualHsCsrs
VmCpuRegisters --> GuestVsCsrs
VmCpuRegisters --> HypervisorCpuState
VmCpuRegisters --> VmCpuTrapState
VmCpuTrapState --> TrapCsrList

Sources: src/regs.rs(L1 - L197) 

General Purpose Register Management

The GeneralPurposeRegisters structure provides a type-safe wrapper around the 32 RISC-V general-purpose registers, using the GprIndex enum for meaningful register names.

Register CategoryRegistersPurpose
Argument registersA0-A7Function arguments and return values
Saved registersS0-S11Callee-saved registers
Temporary registersT0-T6Caller-saved temporary values
Special registersZero, RA, SP, GP, TPZero, return address, stack pointer, global pointer, thread pointer

The GeneralPurposeRegisters implementation enforces RISC-V semantics, particularly that writes to the zero register (GprIndex::Zero) are ignored.

flowchart TD
GprAccess["Register Access Methods"]
reg["reg(GprIndex) -> usizeRead register value"]
set_reg["set_reg(GprIndex, usize)Write register value(ignores Zero writes)"]
a_regs["a_regs() -> &[usize]Get A0-A7 slice"]
a_regs_mut["a_regs_mut() -> &mut [usize]Get mutable A0-A7 slice"]
GprIndex["GprIndex"]
Zero["Zero (x0)"]
RA["RA (x1)"]
SP["SP (x2)"]
A0A7["A0-A7 (x10-x17)"]
T0T6["T0-T6 (x5-x7, x28-x31)"]
S0S11["S0-S11 (x8-x9, x18-x27)"]

GprAccess --> a_regs
GprAccess --> a_regs_mut
GprAccess --> reg
GprAccess --> set_reg
GprIndex --> A0A7
GprIndex --> RA
GprIndex --> S0S11
GprIndex --> SP
GprIndex --> T0T6
GprIndex --> Zero

Sources: src/regs.rs(L1 - L114) 

Register Categories and Organization

The register management system organizes CPU state into five distinct categories, each serving different purposes in the virtualization lifecycle.

Hypervisor vs Guest Register State

flowchart TD
subgraph subGraph2["Virtualization-Specific State"]
    VsCsrs["GuestVsCsrsActive when V=1"]
    VirtHsCsrs["GuestVirtualHsCsrsEmulated hypervisor state"]
    TrapCsrs["VmCpuTrapStateSet on VM exit"]
end
subgraph subGraph1["Guest State"]
    GuestRegs["GuestCpuStateRestored when entering VM"]
    GuestGprs["GPRs: Guest register values"]
    GuestSstatus["sstatus: Guest supervisor status"]
    GuestHstatus["hstatus: Guest hypervisor status"]
    GuestSepc["sepc: Guest program counter"]
end
subgraph subGraph0["Hypervisor State"]
    HypRegs["HypervisorCpuStateSaved when entering VM"]
    HypGprs["GPRs: Host register values"]
    HypSstatus["sstatus: Host supervisor status"]
    HypHstatus["hstatus: Host hypervisor status"]
    HypOther["scounteren, stvec, sscratch"]
end
VMEntry["VM Entry"]
VMExit["VM Exit"]

GuestRegs --> GuestGprs
GuestRegs --> GuestHstatus
GuestRegs --> GuestSepc
GuestRegs --> GuestSstatus
HypRegs --> HypGprs
HypRegs --> HypHstatus
HypRegs --> HypOther
HypRegs --> HypSstatus
VMEntry --> GuestRegs
VMEntry --> HypRegs
VMEntry --> VsCsrs
VMExit --> HypRegs
VMExit --> TrapCsrs

Sources: src/regs.rs(L116 - L196) 

Privilege Level and Usage Context

StructureUsage ContextSave/Restore Timing
HypervisorCpuStateHost execution stateSaved on VM entry, restored on VM exit
GuestCpuStateGuest execution stateRestored on VM entry, saved on VM exit
GuestVsCsrsVS-mode virtualizationActive when V=1, context switched between VMs
GuestVirtualHsCsrsEmulated hypervisorMaintained per-VM for nested virtualization
VmCpuTrapStateTrap informationWritten by hardware on VM exit

Register Access Methods

The VCPU provides several interfaces for accessing and manipulating register state, with different methods for different use cases.

VCPU Register Access Interface

flowchart TD
subgraph subGraph1["Internal Register State"]
    VmCpuRegs["VmCpuRegistersself.regs"]
    GuestGprs["guest_regs.gprs"]
    GuestSepc["guest_regs.sepc"]
end
subgraph subGraph0["Public VCPU Methods"]
    GetGpr["get_gpr(GprIndex) -> usizeRead guest GPR"]
    SetGpr["set_gpr_from_gpr_index(GprIndex, usize)Write guest GPR"]
    SetGprIndex["set_gpr(index: usize, val: usize)Write GPR by numeric index"]
    AdvancePc["advance_pc(instr_len: usize)Increment guest PC"]
    RegsAccess["regs() -> &mut VmCpuRegistersDirect register access"]
end

AdvancePc --> GuestSepc
GetGpr --> GuestGprs
RegsAccess --> VmCpuRegs
SetGpr --> GuestGprs
SetGprIndex --> GuestGprs
VmCpuRegs --> GuestGprs
VmCpuRegs --> GuestSepc

Sources: src/vcpu.rs(L129 - L163) 

Register Initialization During VCPU Creation

The VCPU constructor initializes guest registers with boot parameters, setting up the initial execution environment for the guest VM.

flowchart TD
subgraph subGraph0["Boot Parameters"]
    HartId["A0 = config.hart_idHardware thread ID"]
    DtbAddr["A1 = config.dtb_addrDevice tree blob address"]
end
VCpuNew["RISCVVCpu::new(config)"]
CreateRegs["VmCpuRegisters::default()"]
SetA0["regs.guest_regs.gprs.set_reg(A0, hart_id)"]
SetA1["regs.guest_regs.gprs.set_reg(A1, dtb_addr)"]
VCpuReady["VCPU ready with initialized registers"]

CreateRegs --> SetA0
SetA0 --> HartId
SetA0 --> SetA1
SetA1 --> DtbAddr
SetA1 --> VCpuReady
VCpuNew --> CreateRegs

Sources: src/vcpu.rs(L47 - L62) 

Register Lifecycle and Context Switching

Register state undergoes several transitions during VM execution, with different register categories being saved and restored at different points in the virtualization lifecycle.

VM Entry and Exit Register Flow

sequenceDiagram
    participant HostExecution as "Host Execution"
    participant RISCVVCpu as "RISCVVCpu"
    participant _run_guest as "_run_guest"
    participant GuestExecution as "Guest Execution"

    HostExecution ->> RISCVVCpu: run()
    RISCVVCpu ->> RISCVVCpu: Setup interrupt state (sie, sstatus)
    RISCVVCpu ->> _run_guest: _run_guest(&mut self.regs)
    Note over _run_guest: Save hypervisor state to hyp_regs<br>Restore guest state from guest_regs<br>Load VS CSRs, virtual HS CSRs
    _run_guest ->> GuestExecution: Enter guest mode
    GuestExecution ->> GuestExecution: Execute guest code
    GuestExecution ->> _run_guest: VM Exit (trap, interrupt, ecall)
    Note over _run_guest: Save guest state to guest_regs<br>Restore hypervisor state from hyp_regs<br>Update trap_csrs with exit info
    _run_guest ->> RISCVVCpu: Return to hypervisor
    RISCVVCpu ->> RISCVVCpu: vmexit_handler()
    RISCVVCpu ->> RISCVVCpu: Read trap CSRs (scause, stval, htval, htinst)
    RISCVVCpu ->> HostExecution: Return AxVCpuExitReason

Sources: src/vcpu.rs(L92 - L111)  src/vcpu.rs(L167 - L181) 

Register State During SBI Call Processing

When the guest makes an SBI call, the VCPU accesses argument registers and modifies return value registers according to the SBI specification.

flowchart TD
SbiCall["Guest SBI Call (ecall)"]
ReadArgs["Read arguments from A0-A7a_regs() -> [a0..a7]"]
ExtractIds["Extract extension_id (A7)Extract function_id (A6)"]
ProcessCall["Process SBI call"]
LegacyTimer["Legacy Timerset_timer(a0)"]
LegacyConsole["Legacy Consoleputchar/getchar"]
HsmCalls["HSM Extensionhart_start/stop/suspend"]
Hypercall["HypercallCustom extension"]
ForwardSbi["Forward to RustSBI"]
SetA0Zero["set_gpr_from_gpr_index(A0, 0)"]
SetA0Result["set_gpr_from_gpr_index(A0, result)"]
SetRetVals["set_gpr_from_gpr_index(A0, error)set_gpr_from_gpr_index(A1, value)"]
AdvancePC["advance_pc(4)"]
ContinueGuest["Continue guest execution"]

AdvancePC --> ContinueGuest
ExtractIds --> ProcessCall
ForwardSbi --> SetRetVals
LegacyConsole --> SetA0Result
LegacyTimer --> SetA0Zero
ProcessCall --> ForwardSbi
ProcessCall --> HsmCalls
ProcessCall --> Hypercall
ProcessCall --> LegacyConsole
ProcessCall --> LegacyTimer
ReadArgs --> ExtractIds
SbiCall --> ReadArgs
SetA0Result --> AdvancePC
SetA0Zero --> AdvancePC
SetRetVals --> AdvancePC

Sources: src/vcpu.rs(L184 - L277) 

The register management system provides the foundation for all VCPU operations, maintaining the complete CPU state needed for virtualizing RISC-V guests. The structured approach to organizing different categories of register state enables efficient context switching and proper isolation between hypervisor and guest execution environments.