在 C++ 的功能 ISA 模拟器中实现陷阱(exceptions/intterupts)

Implementation of traps(exceptions/intterupts) at functional ISA simulator at C++

我尝试实现功能性 ISA 模拟器:目标是 RISC-V 和 MIPS。 它是一步一步的指令解释器。

抽象步骤:

while(num_steps)
{
    try
    {
        take_interrupt();// take pending interrupts
        fetch(); // fetch instruction from memory
        decode(); // find handler to instruction
        execute(); // perform instruction
    } 
    catch (Trap& e)
    {
        take_trap(e); //configure appropriate system registers and jump to trap vector.
    }
}

如您所见,C++ 异常用于传输控制流。 也许可以有更帅气的设计?

问题:什么是最好的way/practise 在功能性 ISA 模拟器上实现陷阱。我也对 exceptions/trap 在翻译模拟器上的实现感兴趣,比如 QEMU。

注意: 字词 trap 我的意思是 ISA 定义的陷阱,而不是应用程序错误:未对齐的内存访问、非法指令、系统寄存器访问错误、权限级别更改等

QEMU 是用 C 语言编写的,因此它不使用 C++ 异常。您也不必通过 C++ 异常处理 ISA 陷阱。当对您作为实施者有用时,应该使用异常,仅此而已。

另请注意,陷阱并没有什么特别之处,它们仍然是模拟系统工作流程的一部分。像这样对除法进行编码是完全合法的:

if (reg[divisor] != 0)
  reg[target] = reg[divident] / reg[divisor];
else
  trap(TRAP_DIV0)

其中 trap() 函数直接更新架构状态,以便下一条要模拟的指令来自异常处理程序。

void trap(int trap_id)
{
  // save relevant registers according to platform spec
  ...

  // set instruction pointer to trap handler start
  reg[IP_INDEX] = trap_table[trap_id].ip;

  // update other registers according to spec
  ...
}

C++ 异常可以让您的生活更轻松。例如,许多平台上的内存访问需要将虚拟地址转换为物理地址。这种转换可能会导致陷阱(由于访问不足或配置错误)。写起来可能更容易:

void some_isa_instruction_handler()
{
  int value1 = read_memory(address1);
  int value2 = read_memory(address2);
  int res = perform_something(value1, value2);
  write_memory(address3, res);
}

其中 read_memory()write_memory() 会在需要 ISA 陷阱时简单地抛出 C++ 异常,而不是手动检查每个操作是否已生成陷阱。然后 take_trap() 函数将回滚中断指令处理程序执行的任何更改(如果需要)并设置执行以像上面 trap() 那样模拟陷阱处理程序。

模拟 CISC 系统可能会从这种风格中获益更多。

QEMU 使用 C setjmp()/longjmp() 机制来处理大多数异常:当我们检测到页面错误之类的东西时,我们设置一些标志来指示异常类型,然后 longjmp() 输出到顶级 "execute code" 循环。该循环然后查看标志并在继续执行访客代码之前为 "enter exception handler" 设置 CPU 状态。

所以我们使用 C 等价物来抛出异常;正如 NonNumeric 所说,不需要像这样实现来宾异常(名称的巧合只是巧合)。但是由于触发页面错误的内存访问是不常见的情况,longjmp 或抛出 C++ 异常比在所有内存访问代码路径中包含 "handle failure return" 更有效。来宾内存访问是一个特殊的热点,QEMU 使用一些自定义内联汇编实现其内存访问快速路径,因此我们关心在页面错误时退出到顶级循环而不执行 longjmp 所需的额外指令.使用简单 "fetch/decode/execute" 循环而不对访客代码执行 JIT 的模拟器不太关心性能,因此您的选择可能归结为对代码样式和可维护性的偏好。