为什么会出现“relocation truncated to fit: R_RISCV_JAL ”?

relocation truncated to fit: R_RISCV_JAL against `.L314'

编译 mavpich2 报的错。

我查了一下。貌似是 riscv 的连接器默认只能处理 20bit + 12bit 的位置信息,导致实际上只能连接上下 2G 范围的函数地址。也就是说,一个 64bit 的操作系统,实际上程序只能用 32bit 的程序内存范围?
这个限制怎么取消?我看有人推荐用 -mcmodel=medany 。我查了一下,这东西不保证 PIE 下的可靠性?
还有人说用 -mno-relax ,这东西我看是不进行“优化”,这和函数表的跳转范围无关吧?

这种问题,目前是怎么解决最好的?
我尝试用 -g0 编译了。貌似体积还是没有压下来。

1 Like

另外关于在riscv架构上面构建mavpich2, 好像已经有实现了:[构建失败] mvapich2 · Issue #234 · isrc-cas/tarsier-oerv · GitHub

1 Like

我之前已经编译成了 2.3.6 没这个问题。
现在 centos steam 9 的 2.3.7 才出的这个问题。

这个 PDF 最后还是说用 medany 啊。但是也没有明说为什么和实际问题。
GCC 的文档说这东西不保证 PIE 下面的可靠性。

今天重新编译了 mvapich2 ,加上了 -mcmodel=medany 参数。
但是没用 :face_exhaling:,还是提示:

/tmp/ccj3o8jr.ltrans19.ltrans.o: in function `.L0 ':
/builddir/build/BUILD/mvapich2-2.3.7-1/default/src/mpi/coll/bcast_tuning.c:1034:(.text+0x3d2): relocation truncated to fit: R_RISCV_JAL against `.L314'
collect2: error: ld returned 1 exit status

难道是 lto 的问题?

1 Like

https://sourceware.org/bugzilla//show_bug.cgi?id=30275

呵呵,原来 OpenSUSE 也遇到了相同的问题。 :face_exhaling:

1 Like

Gentoo 似乎也遇到了相同的问题,不过 Gentoo 是编译 webkit-gtk 不通过(而且还是在 qemu 里面):
https://bugs.gentoo.org/860351

我现在只能选择放弃了。 :face_exhaling:
取消 -g 优化参数用 -Os ,替换 mcmodel ,{no-,}relax 都试过不行。

2 个猜测。
第一个是编译器的问题,有 bug 。
第二个应该很扯,就是 riscv64 设计有问题。导致程序只能寻址一个 4G 的程序空间,其他内存空间只能作为数据空间使用。或者是 riscv64 没有考虑到程序编译过程中的问题,需要做切换页来实现全部 64bit 寻址的时候,编译器无法自己识别。导致实际程序依然选择用 20+12 共 32bit 的 auipc/jalr 寻址,或者甚至只用 21 位只有 2M 空间寻址的函数调用指令 jar。

我倾向于第二个,虽然很扯。
因为函数入口如果用 pic (我没有去掉这个),就必须依赖于 got/plt 表。而查询 got/plt ,就必然需要专用的位置,那么 ±2M 似乎好像差不多够了。但是实际上如果程序里面有一大堆函数,而且还是封装成一个大号动态库,似乎超容量很容易了。
gentoo 的 webkit 报错,应该就是因为 webkitgtk 最终就是生成一个大号 so 库文件。
我编译 mvapich2 不通过,恰好也是生成大号 so 的时候。

1 Like

是的我同意
https://stackoverflow.com/questions/56874101/how-does-risc-v-variable-length-of-instruction-work-in-detail

“The code for a executable program image can be up to 4GB in size and still use pc-relative branching — it would use what we refer to as far branches, where the branch sequence is composed of two instructions (auipc and jal ). To be clear, 4GB is a very large code segment. Most of the value of a 64-bit architecture is being able to work with over 4GB of data, not over 4GB of code. To reach code sizes over 4GB, you would use pointers (e.g. stored in tables), since pointers can be full 64-bits wide. This technique is already used for DLLs (even though they generally won’t come close to exceeding 4GB of code when the size of each is added together) since they are usually loaded independently (and thus while pc-relative branches will work inside a single code section, it won’t work to go in between code sections)”

上述文本的谷歌翻译:
“可执行程序映像的代码大小可达 4GB,并且仍然使用 pc 相对分支 - 它将使用我们所说的远分支,其中分支序列由两条指令(auipc 和 jal)组成。 需要明确的是,4GB 是一个非常大的代码段。 64 位架构的大部分价值在于能够处理超过 4GB 的数据,而不是超过 4GB 的代码。 要达到超过 4GB 的代码大小,您可以使用指针(例如存储在表中),因为指针可以是完整的 64 位宽。 这种技术已经用于 DLL(尽管当每个 DLL 的大小加在一起时,它们通常不会接近超过 4GB 的代码),因为它们通常是独立加载的(因此,虽然 pc 相关分支将在单个 代码部分,在代码部分之间进入是行不通的)。”

1 Like

但是程序为了提高性能,如果大量使用嵌入宏和函数,突破 4G 很容易。
尤其是那些大型的本来函数就非常多的应用,降低数量导致容量暴增很合理吧。
假设 10M 一个处理函数,100 个就 1G,400 个大函数内存就满了。

话说,能不能把那些混进 risc-v 社区的 IoT 群体从窗户仍出去?
4G 程序内存这个问题,其实简单的增加一个分页机制就行的事情。

啊。发现一个问题。
其实 risc-v 设计上本身就已经考虑了扩展能力,并不是真的只能寻址 4G 。 :face_exhaling:

其中 mcmlow 是用的绝对寻址,而且寻址长度只有 32bit 。所以 rv64 只能寻址内存最开始的 2G ,和高位内存最后 2G ,一共 4G (分开是因为 64 bit 的内存实际无法完全访问。现在只是实现了 sv36/sv48 长度的寻址,所以为了兼容,最高位 bit 位置相同,进而让内存访问范围成了双向增长的模式)。这个瓶颈是无法突破的。
而 mcmany 是相对寻址,也就是当前程序运行指令位置的前后 2G 一共 4G 空间进行寻址。也就是说,这个功能,并不是真的就限制了 4G 空间,而是跳转范围只有 4G 内。

所以,rv64 使用 mcmany 其实可以寻址所有程序空间,只是不能任意位置进行跳转。所以需要借助 GOT 表来提供过渡跳转实现范围。

那么……其实 GOT 表在 PIE/PIC 环境下,其实是必备的,用来存储内存地址随机化后的位置。
可是现在的问题是,因为 mcmany 依然只有上下 2G 空间。所以实际上要么需要用多重 GOT 表来实现“程序内存分页”的跳转访问(每个内存分页有自己的 GOT 表和处理程序),要么就需要程序先跳转到某个“钩子”节点后,再进行二次跳转。
这就需要编译器提供这种分页机制的程序输出。当然,这种情况下的寻址性能损失很大,但是如果程序只有 1 页,多 GOT 表方式其实性能是没有变化的。

所以……哪个编译器和版本支持? :thinking:

我靠我看到一个说法,说编译 gcc 的时候 --with-cmodel=medany 再编译系统,让整个系统都运行在 any 的环境下才能解决这些超宽问题???? :face_with_raised_eyebrow:

编译 RISC-V 时阅读 GCC 文档
https://gcc.gnu.org/onlinedocs/gcc-13.2.0/gcc/RISC-V-Options.html

`-mcmodel=medlow`

Generate code for the medium-low code model. The program and its statically defined symbols must lie within a single 2 GiB address range and must lie between absolute addresses −2 GiB and +2 GiB. Programs can be statically or dynamically linked. This is the default code model.

`-mcmodel=medany`

Generate code for the medium-any code model. The program and its statically defined symbols must be within any single 2 GiB address range. Programs can be statically or dynamically linked.

The code generated by the medium-any code model is position-independent, but is not guaranteed to function correctly when linked into position-independent executables or libraries.

谷歌翻译/Google translate:

-mcmodel=medlow
为中低代码模型生成代码。 程序及其静态定义的符号必须位于单个 2 GiB 地址范围内,并且必须位于绝对地址 -2 GiB 和 +2 GiB 之间。 程序可以静态或动态链接。 这是默认的代码模型。

-mcmodel=medany
为介质-任意代码模型生成代码。 程序及其静态定义的符号必须位于任何单个 2 GiB 地址范围内。 程序可以静态或动态链接。

由介质-任意代码模型生成的代码是位置无关的,但不能保证在链接到位置无关的可执行文件或库时正确运行。

这个文档我早就看了。

问题在于加上 medany 后,依然报错。 :face_exhaling:

https://mvapich.cse.ohio-state.edu/help/

这不是 mvapich2 的事情啊,这是 riscv 设计上的问题。

而且还一点 mvapich2 其实是不支持 riscv 的,只是这东西没有那么多 cpu 指令集绑定内容,所以只需要一个小 patch 修正必须用指令实现的获取时钟周期的函数。
不然我早就能从 opensuse 那边拿到 patch 了。

程序实际上需要超过 2 GiB 的机器代码的情况有多频繁。

目前 RV32 和 RV64 指令仅使用 32 位。 主要区别在于 RV64 有 64 位寄存器,而 RV32 只有 32 位寄存器。 RV128 会一样吗(32 位指令和 128 位寄存器)?

解决此问题的一种方法是使用 64 位寄存器来更新程序计数器(这会影响性能),强制长跳转到另一个 2 GiB 的机器代码块。 如果现在需要的话,应该可以使用小块内联 RISC-V 汇编代码来创建此类补丁。 这可能不是一个完美的解决方案,但它是可以做到的。

也许稍后会添加新的 RISC-V 扩展,以支持需要 2 GiB 以上 RAM 的程序代码。 或者也许RV128不仅会增加128位寄存器,还会进行其他改变。

现在 riscv 的跳转,是两次指令用内嵌的数据实现凑齐的。
本身限制不是寄存器的事情,而是指令长度只有 32bit 导致的。因为指令描述本设计就有占用,所以就只能两条指令凑 32bit 。
我觉得增加一个段落更符合这个风格,未来可以增加新的指令或者进行替换来扩容。这样不用新的东西,就是旧的状态。兼容性更好。
但难题主要是现在的软件实现,如何能更好的兼容未来的硬件实现。
不能兼容未来设计的补丁肯定不行。最佳选择我觉得应该是把这个任务扔给链接库解释器去做。这样未来只需要替换解释器就能实现硬件实现的兼容。

不过,这对于我来说,还是太久远了。现在只能认为是 mvapich2 真的不能支持 riscv64 了。

1 Like