Skip to content

iss: Support custom TB CPU State in VADL spec #776

@Jozott00

Description

@Jozott00

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.

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

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 call
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.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestissThis is ISS relatedlanguageLanguage changesoptimizationThis is an optimization of an existing feature

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions