ARM,VFP,浮点数,惰性上下文切换

3

我正在为ARM处理器(Cortex-A9)编写操作系统。

我试图实现浮点寄存器的惰性上下文切换。这背后的思路是,对于一个线程,浮点扩展最初是禁用的,因此在任务切换时无需保存浮点上下文。

当一个线程尝试使用浮点指令时,它会触发异常。然后操作系统启用浮点扩展,并知道必须在下一次上下文切换中保存该线程的浮点上下文。然后重新执行浮点指令。

我的问题是,即使在c代码中未使用浮点运算,编译器仍会生成浮点指令。以下是一个不使用浮点的函数的反汇编示例:

10002f5c <rmtcpy_from>:
10002f5c:   e1a0c00d    mov ip, sp
10002f60:   e92ddff0    push    {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}
10002f64:   e24cb004    sub fp, ip, #4
10002f68:   ed2d8b02    vpush   {d8}
...
10002f80:   ee082a10    vmov    s16, r2
...
10002fe0:   ee180a10    vmov    r0, s16
...
1000308c:   ecbc8b02    vldmia  ip!, {d8}
...

当我有许多这样的函数时,懒惰的上下文切换就没有意义了。

有人知道如何告诉编译器只在c代码中有浮点操作时才生成浮点指令吗?

我使用的是gcc 9.2.0。浮点选项为:-mhard-float -mfloat-abi=hard -mfpu=vfp

这里是一个示例c函数(不可用,仅为演示):

void func(char *a1, char *a2, char *a3);
int bar_1[1], foo_1, foo_2;

void fpu_test() {
    int oldest_idx = -1;
    while (1) {
        int *oldest = (int *)0;
        int idx = oldest_idx;
        for (int i = 0; i < 3; i++) {
            if (++idx >= 3)
                idx = 0;
            int *lec = &bar_1[idx];
            if (*lec) {
                if (*lec - *oldest < 0) {
                    oldest = lec;
                    oldest_idx = idx;
                }
            }
        }
        if (oldest) {
            foo_1++;
            if (foo_2)
                func("1", "2", "3");
        }
    }
}

gcc命令行:

$HOME/devel/opt/cross-musl/bin/arm-linux-musleabihf-gcc  -O2 -march=armv7-a -mtune=cortex-a9 -mhard-float -mfloat-abi=hard -mfpu=vfp -Wa,-ahlms=fpu_test.lst -mapcs-frame -c fpu_test.c -o fpu_test.o

汇编代码清单:

...
  35 0000 0DC0A0E1      mov ip, sp
  36 0004 003000E3      movw    r3, #:lower16:foo_2
  37 0008 F0DF2DE9      push    {r4, r5, r6, r7, r8, r9, r10, fp, ip, lr, pc}
  38 000c 006000E3      movw    r6, #:lower16:foo_1
  39 0010 003040E3      movt    r3, #:upper16:foo_2
  40 0014 04B04CE2      sub fp, ip, #4
  41 0018 006040E3      movt    r6, #:upper16:foo_1
  42 001c 004000E3      movw    r4, #:lower16:bar_1
  43 0020 028B2DED      vpush.64    {d8}                <=== this is the problem
...

我在帖子中更新了一个带有gcc命令行和部分汇编列表的C函数示例。 - undefined
-mapcs-frame 生成符合ARM过程调用标准的堆栈帧,即使对于代码的正确执行来说并不严格必要。使用此选项指定-fomit-frame-pointer会导致叶子函数不生成堆栈帧。默认选项为-mno-apcs-frame。此选项已被弃用。 - undefined
1
如果我移除那个,那么这个推送也会消失(同时也浪费了一个寄存器的栈帧)。 - undefined
去掉 -mapcs-frame 对于简单的函数是有效的。在我的项目中,vpush 指令的数量减少了,但它们仍然存在。 - undefined
是否没有PSR位来指示自上次重置(例如在上下文切换时)以来已写入浮点寄存器?这是其他架构上常用的惰性FP上下文切换方式(至少是保存部分 - 仍需要异常处理读取)。 - undefined
显示剩余2条评论
2个回答

1

GCC有一个命令行开关,-mgeneral-regs-only,可以用于此问题。使用该命令行开关时,您可能需要将有意使用浮点寄存器或操作的代码分离到单独的源文件中,以便可以在没有该开关的情况下进行编译。

自GCC 9.3(也许是9?)起,针对ARM目标,这可作为函数属性使用:

void MyFunction(char *MyParameter) __attribute__ ((general-regs-only));

将属性放在声明之后是一种较旧的语法,需要一个非定义声明。测试表明,GCC现在接受在声明符之前使用属性,并且可以与定义一起使用。
void __attribute__ ((general-regs-only)) MyFunction(char *MyParameter)
{...}

你可能还可以使用__attribute__ ((nogeneral-regs-only))来否定属性,尽管我没有看到这方面的文档说明。
这也可以通过pragma进行控制。
-march-mcpu开关中也有+nofp选项,但我认为-mgeneral-regs-only是你想要的。

是的,-mgeneral-regs-only编译C函数时不包含浮点数,生成的代码中没有浮点寄存器。但是这样我就必须将C代码分成有和没有浮点数的文件。这不是我想要的。 - undefined
它在'attribute ((general-regs-only))'处出现了错误。可能在gcc 9.2.0中不可用。尽管如此,这个属性迫使我去修改源代码,而我想避免这样做。 - undefined
1
@ErwinP:如果您无法使用命令行开关且不想修改源代码,我对您能找到解决方案并不抱太大希望。 - undefined

1

关于函数属性的注意事项: __attribute__ ((general-regs-only)) 在gcc 9.3.1上似乎无效。

尝试使用'target'属性。例如:

void __attribute__((target("general-regs-only"))) MyFunction(char *MyParameter)
{...}

在gcc 9.3.1上,"general-regs-only"的否定似乎无效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接