-
Notifications
You must be signed in to change notification settings - Fork 2
iss: Support custom TB CPU State in VADL spec #776
Description
In instruction behavior specifications, it’s quite common for the behavior of an instruction to depend on a register value that represents a flag. This is particularly true for privileged instructions that are illegal to execute in user mode and must only be executed in kernel mode.
Another example is the MSR.sf flag, which indicates that the CPU is operating in 64-bit mode. This flag is checked during the behavior definition and triggers different behaviors based on its value.
Lines 683 to 692 in dad7ee4
| model BFormBranchInstrExt (asm : Str, lk : Bool, aa : Bool) : IsaDefs = { | |
| instruction AsId($asm, $laStr($lk; $aa)) : BForm = { | |
| $CS($lk; LR := PC.next) | |
| let nctr = if bo(2) then CTR else CTR - 1 in | |
| let mctr = if MSR.sf = Mode::M32 then nctr & maxU32E else nctr in { | |
| if (bo(2) | ((mctr != 0) ^ bo(1))) & (bo(4) | ($CRBit(crbs) = bo(3))) | |
| then PC := $TargetAddrImm(extBd; $aa) | |
| CTR := nctr | |
| } | |
| } |
Since this flag is part of the MSR register and is not known during translation, the generated translation function will emit TCG operations to check the sf flag and operations of all expressions that depend on it.
While this approach is correct, it is quite inefficient because the sf flag is rarely modified. To address this, upstream implementations integrate the sf flag into the TB CPU state. During TB lookup, if QEMU encounters a TB state for a specific PC, it ensures that this TB state aligns with the inferred state from the current CPU state.
So, in the case of sf, when a TB is translated, it is associated with the sf being 0. This implies that all instructions in this TB can assume that reading sf results in 0, allowing the check and all expressions dependent on it to be computed during translation (as C expressions).
However, if sf is modified by an instruction, the TB ends with this instruction because the value of sf might no longer be 0.
Now, if we perform a TB lookup for a specific PC and find one, we check if the found TB’s sf state matches the sf of the CPU state. If this is the case, we can simply jump to the TB. Otherwise, we must create a new TB that assumes sf to be 1.
This state comparison between TBs is a fundamental QEMU execution abstraction that operates target-agnostically. The state is retrieved by invoking the target-specific function
openvadl/vadl/main/resources/templates/iss/target/gen-arch/cpu.h
Lines 69 to 75 in dad7ee4
| static inline void cpu_get_tb_cpu_state(CPU[(${gen_arch_upper})]State *env, vaddr *pc, | |
| uint64_t *cs_base, uint32_t *pflags) | |
| { | |
| *pc = env->[(${gen_arch_upper})]_PC; | |
| *cs_base = 0; | |
| *pflags = 0; | |
| } |
which is in our case trivial (just the PC). The target implementation encodes the state into the
cs_base and pflags.All the values encoded in the state, are decoded and stored in the
DisasContext during the TB DisasContext initialization callopenvadl/vadl/main/resources/templates/iss/target/gen-arch/translate.c
Lines 198 to 205 in dad7ee4
| static void [(${gen_arch_lower})]_tr_init_disas_context(DisasContextBase *db, CPUState *cs) | |
| { | |
| DisasContext *ctx = container_of(db, DisasContext, base); | |
| CPU[(${gen_arch_upper})]State *env = cpu_env(cs); | |
| [(${gen_arch_upper})]CPUClass *mcc = [(${gen_arch_upper})]_CPU_GET_CLASS(cs); | |
| [(${gen_arch_upper})]CPU *cpu = [(${gen_arch_upper})]_CPU(cs); | |
| ctx->env = env; |
(currently we do nothing really)
Then, when an instruction needs the value of such state, it can read it as part of the
DisasContext. This is an examples of RISC-V which checks if the current privilege mode is user-mode
static bool trans_ebreak(DisasContext *ctx, arg_ebreak *a) {
// ...
if (semihosting_enabled(ctx->priv == PRV_U)) { /* ... */ }
// ...
}We want to allow VADL users to define such TB CPU state to increase the generated QEMU performance dramatically.