LoongArch 的 LBT 扩展也相当好玩,为了方便之后对比手动模拟 eflags 和直接使用 LBT 扩展的性能差距,我们同时实现了这两套方案。我已经等不及实现足够的指令后 benchmark 一下了
🤯1
LoongArch VSCode 折腾小计
最近因为需要在 LoongArch 上写一些 C 代码,所以折腾了一下 VSCode 环境。
本体下载:
https://github.com/Xiao-Tao/vscode-LA64/releases
在 Loongson 群里搜到的群友打包的 code-oss,解压就能用,版本是 1.80.1。
VSCode 插件:
因为没有 marketplace,所以需要手动下载 VSIX 文件来安装。
- GitLens:
https://github.com/gitkraken/vscode-gitlens/releases/tag/v13.0.0
需要下载旧版本,新版本不兼容。我用的是 13.0.0,再新一点的可能也能用,但我懒得试了。
- LoongArch Assembly:
https://github.com/FreeFlyingSheep/loongarch-assembly/releases
汇编语法高亮。
- C/C++ IntelliSense
https://github.com/microsoft/vscode-cpptools/releases
直接下载最新版的 cpptools-linux.vsix (x86_64)安装即可正常使用(需要安装 latx)。
最近因为需要在 LoongArch 上写一些 C 代码,所以折腾了一下 VSCode 环境。
本体下载:
https://github.com/Xiao-Tao/vscode-LA64/releases
在 Loongson 群里搜到的群友打包的 code-oss,解压就能用,版本是 1.80.1。
VSCode 插件:
因为没有 marketplace,所以需要手动下载 VSIX 文件来安装。
- GitLens:
https://github.com/gitkraken/vscode-gitlens/releases/tag/v13.0.0
需要下载旧版本,新版本不兼容。我用的是 13.0.0,再新一点的可能也能用,但我懒得试了。
- LoongArch Assembly:
https://github.com/FreeFlyingSheep/loongarch-assembly/releases
汇编语法高亮。
- C/C++ IntelliSense
https://github.com/microsoft/vscode-cpptools/releases
直接下载最新版的 cpptools-linux.vsix (x86_64)安装即可正常使用(需要安装 latx)。
box64 中对于 Self-Modifying Code™ 的处理:
所有的 guest 代码页都被设置为了 read-only,所以当有指令试图写入到 guest 代码页的时候,就会当场触发一个 SIGSEGV。
box64 的全局 signalhandler 接收到 SIGSEGV 信号后,首先会搜索出事的 pc 是否在某个 dynablock 中,如果确实是来自 dynablock,就会检查 si_addr 是否属于 guest 代码页,两者都吻合,就会 --
1. 把那个 dynaloblock 设置为 dirty;
2. 然后将相应的 guest 代码页设置为可写;
3. 从 ucontext 里面把 x86 状态恢复出来[1];
4. siglongjump 回
5. 解释器会尽快退出执行,跳转回 DynaRec。
对于任何标记为 dirty 的 dynablock,DynaRec 会检查 CRC 来确定是不是真的脏了,是的话就重建,否则就重新标记为 clean。
[1] box64 中每一个 x86 的寄存器都被一一映射到了 host 寄存器上,且 codegen 会保证对于每一条指令,生成的代码对于 x86 寄存器写入一定在内存写入之后,所以不用担心, ucontext 里面的状态一定是正确的。
这就是 x86 模拟器支持 SMC 所需要付出的代价。
RISC 则要容易得多,比如对于 RISC-V 模拟器来说,可以选择直接 intercept fence.i 和 __clear_cache() 然后更新相应的 dynablock 即可。
所有的 guest 代码页都被设置为了 read-only,所以当有指令试图写入到 guest 代码页的时候,就会当场触发一个 SIGSEGV。
box64 的全局 signalhandler 接收到 SIGSEGV 信号后,首先会搜索出事的 pc 是否在某个 dynablock 中,如果确实是来自 dynablock,就会检查 si_addr 是否属于 guest 代码页,两者都吻合,就会 --
1. 把那个 dynaloblock 设置为 dirty;
2. 然后将相应的 guest 代码页设置为可写;
3. 从 ucontext 里面把 x86 状态恢复出来[1];
4. siglongjump 回
run_code() ,使用解释器去执行 SMC 代码;5. 解释器会尽快退出执行,跳转回 DynaRec。
对于任何标记为 dirty 的 dynablock,DynaRec 会检查 CRC 来确定是不是真的脏了,是的话就重建,否则就重新标记为 clean。
[1] box64 中每一个 x86 的寄存器都被一一映射到了 host 寄存器上,且 codegen 会保证对于每一条指令,生成的代码对于 x86 寄存器写入一定在内存写入之后,所以不用担心, ucontext 里面的状态一定是正确的。
这就是 x86 模拟器支持 SMC 所需要付出的代价。
RISC 则要容易得多,比如对于 RISC-V 模拟器来说,可以选择直接 intercept fence.i 和 __clear_cache() 然后更新相应的 dynablock 即可。
👏2🎃1
https://github.com/FEX-Emu/FEX/blob/main/docs/DeferredSignals.md
FEX relies on guest signal handlers returning via sigreturn to handle stacked deferred signals, so a longjmp would interfere with this
🤨 所以 “Classical signal masking” 到底是哪里不好。。
FEX relies on guest signal handlers returning via sigreturn to handle stacked deferred signals, so a longjmp would interfere with this
🤨 所以 “Classical signal masking” 到底是哪里不好。。
22:00 开会,21:50 的时候我家猫在他的跑步机上拉了一大泡,然后在上面猛跑把屎甩的到处都是,这会刚收拾完。
都五岁了之前从来没有乱拉过,这是怎么了 🤔️
都五岁了之前从来没有乱拉过,这是怎么了 🤔️
🤔1
今天 GitHub 给我推了一个很有意思的东西 fwsGonzo/libriscv,从名字也能看出个大概,这是一个用户空间的 RISC-V 模拟器库,提供了常规的解释器实现。
但除此之外,它还有一个很有趣的东西:开启某个编译期选项后,libriscv 就会在运行 guest 程序前扫描它的所有代码,把可以被安全翻译的一些片段翻译成 plain C 代码,然后调用本地的 C 编译器编译后放到代码缓存中,供后续的运行时使用。
例如,下面的代码序列的前 4 条指令就可以被安全地翻译:
但最后的
以上是背景。
在扫描器开始工作之前,其实还有一遍可选的扫描,这个是今天的主题。
这遍可选的扫描会去尝试寻找
有人可能一眼就看出来了,这是在找
按照 RISC-V 的 ABI,
既然我们已经知道了这个常量的值,那么每当有指令需要读取
但开启这个优化后,生成的代码就变成了:
很明显,后者更优。
但除此之外,它还有一个很有趣的东西:开启某个编译期选项后,libriscv 就会在运行 guest 程序前扫描它的所有代码,把可以被安全翻译的一些片段翻译成 plain C 代码,然后调用本地的 C 编译器编译后放到代码缓存中,供后续的运行时使用。
例如,下面的代码序列的前 4 条指令就可以被安全地翻译:
1:
li a0, 100
beqz a0, 1f
addi a0, a0, -1
j 1b
1:
ret
但最后的
ret 则无法翻译,因为 ra 的值是编译期未知的,所以扫描器就会终止当前的代码块,跳过这条指令,然后再开始翻译下一段。以上是背景。
在扫描器开始工作之前,其实还有一遍可选的扫描,这个是今天的主题。
这遍可选的扫描会去尝试寻找
auipc gp,imm 指令以及它后面紧跟着的 addi[w] gp,gp,imm 。很明显这两条指令是在给 gp 赋值。然后 libriscv 会把要赋的值算出来,保存起来,这遍扫描就完成了。有人可能一眼就看出来了,这是在找
load_gp :
0000000000023572 <load_gp>:
23572: 0028e197 auipc gp,0x28e
23576: 30618193 addi gp,gp,774 # 2b1878 <__global_pointer$>
2357a: 8082 ret
...
按照 RISC-V 的 ABI,
gp 寄存器是 global pointer ,始终指向 .data 段中的一个固定的地方,用于做链接器的松弛。尽管这个设定非常的嵌入式,但编译器通常也不会把 gp 寄存器挪作他用。所以对于遵循 ABI 的绝大多数 RISC-V 程序而言, gp 寄存器打从程序运行的一开始,其中保存的便是一个常量。既然我们已经知道了这个常量的值,那么每当有指令需要读取
gp 寄存器时,我们就可以安全地替换成字面量。例如对于 addi a0,gp,32 ,通常情况下,生成的 C 代码是:
GPRs[a0] = GPRs[gp] + 32;
但开启这个优化后,生成的代码就变成了:
GPRs[a0] = 0x2b1878 + 32;
很明显,后者更优。
突然接了个 numpy rvv 1.0 的活,今天搭建了一下开发环境。因为没有可用的 rvv 1.0 硬件,所以最后使用的方案是 x86 archlinux + qemu user 8.2 + riscv archlinux rootfs。
VSCode Remote 直接通过 x86 连到 rootfs 的项目目录下,rootfs 里面配置好 clang、python、meson 以及 virtualenv 之类的开发环境,最后获得了一个接近原生的开发体验。
archriscv 的包版本都很新,clang 直接就有 rvv 1.0 instrinsic 的支持,完全不用折腾工具链。唯一的缺点是 qemu user 的性能确实不太够看,风扇狂转但编译并不快。
VSCode Remote 直接通过 x86 连到 rootfs 的项目目录下,rootfs 里面配置好 clang、python、meson 以及 virtualenv 之类的开发环境,最后获得了一个接近原生的开发体验。
archriscv 的包版本都很新,clang 直接就有 rvv 1.0 instrinsic 的支持,完全不用折腾工具链。唯一的缺点是 qemu user 的性能确实不太够看,风扇狂转但编译并不快。
🎉3🥰2🤔1
ksco 的工作日志
突然接了个 numpy rvv 1.0 的活,今天搭建了一下开发环境。因为没有可用的 rvv 1.0 硬件,所以最后使用的方案是 x86 archlinux + qemu user 8.2 + riscv archlinux rootfs。 VSCode Remote 直接通过 x86 连到 rootfs 的项目目录下,rootfs 里面配置好 clang、python、meson 以及 virtualenv 之类的开发环境,最后获得了一个接近原生的开发体验。 archriscv 的包版本都很新,clang…
继续 numpy,因为需要支持最新 rvv intrinsic 的编译器,但 gcc 需要等 14 才有,所以切换到了 clang 17。切换过去后编译完运行测试发现某个测例失败,最后整理出来下面这段代码:
在 clang 编译的 numpy 上,运行这段代码会报错:
于是怀疑是编译器的问题,尝试构造一个最小复现,然后就在 Python 和 C 代码里逛了一晚上,最后终于找到了
这段代码如果用 clang rv64 编译运行会输出 raised,用 gcc rv64 则不会,经过和 clang x64 交叉验证,发现 clang rv64 是有问题的。
import numpy as np
code = 'g' # long double
fnan = np.array(np.nan, dtype=code)
fone = np.array(1.0, dtype=code)
with np.errstate(all='raise'):
print(np.floor_divide(fnan, fone))
在 clang 编译的 numpy 上,运行这段代码会报错:
FloatingPointError: invalid value encountered in floor_divide ,而 gcc 编译器则可以输出预期的 nan 。于是怀疑是编译器的问题,尝试构造一个最小复现,然后就在 Python 和 C 代码里逛了一晚上,最后终于找到了
floor_divide 调用的底层函数 npy_divmodl() ,又一通精简后,构造出如下复现代码:
bool test(long double a, long double b)
{
long double m = fmodl(a, b);
return isless(m, (long double)0);
}
int main(void) {
fetestexcept(FE_INVALID);
long double a = NAN, b = 1.0;
printf("%d\n", test(a, b));
if(fetestexcept(FE_INVALID)) printf("raised\n");
return 0;
}
这段代码如果用 clang rv64 编译运行会输出 raised,用 gcc rv64 则不会,经过和 clang x64 交叉验证,发现 clang rv64 是有问题的。
🤯4
macOS Preview 怎么这么菜,M2 打开 v-intrinsic-spec.pdf 随便搜点东西都直接卡死,也就 4000 页而已。
👌1
刚刚看褪黑素的瓶子上面写着吃完不要驾驶,突然想起来前天晚上吃完之后开美卡,最后一把药劲上来了撞了个损坏度 100%,看来确实没瞎说。
🥱5🤔2