ksco 的工作日志
245 subscribers
168 photos
10 videos
4 files
84 links
内容主要取决于我正在做的东西,目前主要是模拟器 / DBT 之类的散乱话题。
Download Telegram
C 扩展是如何设计的?出镜率很高,感觉挺合理。
🤔3
喜报:DynamoRIO 已经可以运行 box64 和 luajit 了
🥰3👌1
Channel name was changed to «ksco 的工作日志»
茉莉芽白
🤔2
ksco 的工作日志
茉莉芽白
味道像泔水,别买
🤔7
mold debug 小计

今天开发 DynamoRIO RV64 终于受不了 GNU ld 慢出天际的性能,决定掏出 mold 来拯救一下:


mold -run make -j4


然后就发现 mold 竟然 segfault 了,于是就开始了漫长的 debugging。之前给 mold 贡献过一点代码所以算是对 codebase 有一点了解,这方面没有花太多时间。

使用 gcc -v 打印出编译器 driver 调用链接器的命令行,替换成 mold 后运行复现了 segfault。于是 git clone 了 mold 的最新代码,debug build 之后挂上 gdb 拿到了 backtrace。

在 backtrace 前后一通 print 后发现是一个名为 __global_pointer$ 的符号在做 COPYREL 时,因为对应的文件为空,所以触发了 segfault。

__global_pointer$ 是 RISC-V psABI 中规定的一个由链接器在链接期合成的符号,指向 .sdata+0x800 的位置, gp 寄存器会在程序一开始执行的时候就被赋值为 __global_pointer$ 的值并且不会再变,所以用户程序可以方便地通过 gp 寄存器来快速访问 .sdata 数据。

psABI 还规定了如果动态链接的可执行文件中包含 gp -relative 的内存访问,则 __global_pointer$ 必须导出到动态符号表中以供动态链接器使用。

总结一下, __global_pointer$ 由链接器合成,并且在满足条件时,需要导出到可执行文件中的动态符号表中。挺好,看起来没啥问题。

回到问题本身,一通瞎调试和漫长的编译等待后毫无头绪,我突然想到可以精简一下链接命令行,看看能不能缩小一下范围。最后发现只要尝试和某个 .so 链接就可以复现这个问题。然后我惊奇地发现这个 .so 竟然导出了 __global_pointer$ 符号:


$ readelf -s -W xxx.so | grep __global_pointer$
75: 000000007131b5e4 0 NOTYPE GLOBAL DEFAULT ABS __global_pointer$


然后突然想起来同事的科普: .so 文件其实可以同时是一个可执行文件,比如你可以直接执行 /usr/lib/libc.so.6 ,所以动态链接库导出这个符号并不奇怪。

这下问题就说通了,mold 在读取完输入的 object files 之后,会自己合成一堆符号, __global_pointer$ 就是其中之一。但如果读取进来的 object files 已经存在了这个符号,就会和 mold 创建的合成符号产生冲突,那么出现什么问题也都不奇怪了。

总之,最后提交了如下 PR,解决方案是在读取 shared object 符号时,把所有读到的 __global_pointer$ 过滤掉:

https://github.com/rui314/mold/pull/1236

但不知道作者会不会接受这个 PR,因为可能存在更好的修复方式。
🤯3👍1🤔1
省流:
¥499 8 核 PCIe
rv64imafdcv_sscofpmf_sstc_svpbmt_zicbom_zicboz_zicbop_zihintpause
😢2
感谢 @sterpr1m 分享的 spike-dasm 跨架构平替,效果:
$ ./la64dasm 700be063
0: 700be063 vadd.d $vr3, $vr3, $vr24


代码:
$ cat la64dasm 
#!/usr/bin/env bash

TEMP=$(mktemp)

echo $1 | tac -rs .. | echo "00000000: $(tr -d '\n')" | xxd -r > $TEMP
objdump -b binary -m Loongarch64 -D $TEMP | grep 0:
rm $TEMP
🥰4
May the 4th be with you.
🥰71
如何用一天的时间实现 numpy RVV 支持

背景:

numpy 的基本架构是有一套 universal intrinsic,然后各个后端(比如 Neon、SSE)用自己的 intrinsic 去实现这个 universal 的。

方案一:

所以如果要增加 RVV 的支持,比较正统的做法就是增加一个 RVV 的后端,然后去实现这套 universal intrinsic。

但就目前情况来说,上游自己也觉得维护这么多后端的担子太重了,所以他们其实有砍掉整个 universal intrinsic,换用更加成熟的 google/highway 的想法。

这也决定了上游几乎不太可能接受一个巨大的 RVV backend patch 了,方案一属于费力不讨好。

方案二:

既然上游有用 highway 的意愿,那就帮助上游把所有用 universal intrinsic 写的函数都用 highway 重写一遍,因为 highway 支持 RVV,这样 numpy RVV 也就顺手支持了。

方案二最大的问题就是上游推进 highway 支持的进度非常慢,这对其他已有的后端几乎没有影响,但对于 RVV 来说就是有还是无的致命问题。

方案三:

那如果放弃提交上游,有没有更加快捷,不需要完整实现一个 RVV 后端的方法呢?

方案三就是今天要介绍的做法,neon2rvv[1] 是一个将 ARM Neon intrinsic 翻译成 RVV intrinsic 的兼容层,举个例子:


uint32x2_t vadd_u32(uint32x2_t a, uint32x2_t b) {
return __riscv_vadd_vv_u32m1(a, b, 2);
}


可以看到 vadd_u32__riscv_vadd_vv_u32m1 是一对一的翻译,当然也存在一对多的情况。

因为 Neon 的寄存器是 128 位的,这就要求 vlen 必须大于等于 128 位,并且超出 128 位的部分会被完全浪费掉。

本着有总比没有强的原则,使用这个兼容层,我们就可以原地利用 Neon 后端来实现 RVV 后端了,理想情况下,要做的就只是 #include neon2rvv.h

当然,理想情况并不存在,在实际的实现过程中,我发现了一些无法用 RVV 实现的 Neon intrinsic;neon2rvv 尚未实现的 intrinsic;neon2rvv bug 等等。

最终的结果就是:

numpy 加了一个很小的 patch [2],neon2rvv 提交了一个很小的 PR [3],numpy RVV 就有了初步的支持。

在新发布的 BananaPi F3(顺序 8 核,vlen=256) 上运行 pytest 结果如下:


237 failed, 42599 passed


可以看到,目前的支持度还是挺不错的,失败的都是一些比较极端的情况,还需要花更多时间来 debug。

做了一个简单的 benchmark,在 absolute_f32 上对比标量有 4 倍的性能提升。因为这台机器的 vlen 是 256,所以预期值应该是 8 倍,这里 4 倍是因为高 128 位完全没利用起来。

[1] https://github.com/howjmay/neon2rvv
[2] https://github.com/plctlab/numpy/commit/edee45e5e184d23929a605c9441607aca03a019a
[3] https://github.com/howjmay/neon2rvv/pull/396
👍6
接下来是不是可以上 C-Reduce 了
2🤔1
ksco 的工作日志
接下来是不是可以上 C-Reduce 了
跑出来了,精简到了 <60 行:https://ksco.cool/a/wnwr

edit:已交由同事提交到了 Debian:https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1071140
🗿5
正在用 gdb 調試 gdb,感覺我 CPU 快燒了
🤯10
自文档的 Makefile
🥰6
Box64 新版本:
https://github.com/ptitSeb/box64/releases/tag/v0.2.8

「Loongarch dynarec! Linux games are running, and with good speed, especialy on 3A6000 platforms. Games on Wine are not working yet due to 16k pagesize limitation.」
🥰5👍1
TIL 如何计算一个 uint8 的 parity?

(parity 是指一个数字中 1 的个数的奇偶性,偶数结果为 1,奇数则结果为 0)

uint8 a = ...
a ^= (a >> 4)
a ^= (a >> 2)
a ^= (a >> 1)
a &= 1
a ^= 1
return a


其实就是按对来取消所有的 bits,最后留下的 least significant bit 取反就是结果。
🤔1
Forwarded from 刘阳
卧槽
Forwarded from 刘阳
我他妈知道了
Forwarded from 刘阳
龙芯的 amswap* 系列指令前面两个寄存器不能是同一个
😁7
Forwarded from 刘阳
那你倒是报个 sigill 啊