在 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 的模拟器不太关心性能,因此您的选择可能归结为对代码样式和可维护性的偏好。
我尝试实现功能性 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 的模拟器不太关心性能,因此您的选择可能归结为对代码样式和可维护性的偏好。