在.text中不断出现内联而不是从内存加载

时间:2018-05-18 08:32:21

标签: gcc assembly embedded ada sparc

我们基本上使用sparc架构。并使用gcc编译我们的代码。

我们在代码中使用了一些常量,但编译器而不是为某些常量分配内存而是优化并使其成为代码的一部分

例如

cHello : CONSTANT INTEGER_16 := 16#FFFE#;
a = cHello [ cHello is a constant ];

组装如下。

set 16#FFFE#, %g1
sthw %g1, [%g2]

编译器将cHello的值内联到代码(.text)而不是从内存加载。

如何使编译器从内存加载常量而不是将它们内联

编辑:语言是Ada

我为什么关心:

问题在于,我们有一个嵌入式系统,我们的代码实际上是从RAM运行的,而我们希望有一个常量可修改的。我们不想制作.data,因为那时我们会有两份副本;在通电时,它们被复制到可变RAM区域。在这种情况下,常数更好。加上我们执行的RAM从写入锁定。所以我们解锁它然后写入代码RAM。

4 个答案:

答案 0 :(得分:2)

它按预期工作。常量变量在.text(或任何const部分)中分配,但是你想使用RAM作为闪存,RAM是一个有价值的占有。 无论如何,如果你想使用RAM来分配常量值,你可以通过链接器在RAM中创建一个新的部分(.myownconst),并将你的const变量声明为__attribute__((section(".myownconst")))

答案 1 :(得分:2)

这个答案的语法将假设一个最近的(GNAT,Ada2012)编译器,但我毫不怀疑你可以用pragma做同样的事情。

从观察来看,GNAT会将常量变成一个直接的字面值,如果它可以看到它。

GNAT不会让变量变为常量和易变。

我发现强制从存储中取出常量的唯一方法是通过导入变量来欺骗编译器:

with Interfaces;
package Prakhar_Constants is
private
   Chello : constant Interfaces.Integer_16 := 16#7FFE#
   with
     Export,
     External_Name => "constant_chello";
end Prakhar_Constants;

然后

with Interfaces;
with Prakhar_Constants;  -- so the binder will know to include it
procedure Prakhar is
   Chello : constant Interfaces.Integer_16
   with
     Import,
     Convention => Ada,
     External_Name => "constant_chello";
   A : Interfaces.Integer_16;
begin
   A := CHello;
end Prakhar;

我认为你不需要打扰volatile(除非你要在执行中改变“常数”)。

答案 2 :(得分:1)

您可以尝试aliased constant

cHello : aliased constant Interfaces.Integer_16 := 16#FFFE#;

答案 3 :(得分:-1)

我认为您应该在独立的asm或C文件中定义常量的值。这是一种保证编译器在任何地方内联值的保证方法,即使使用链接时优化,也不会像volatile那样使用效率低的任何东西。即,Ada编译器永远不会在任何源文件中看到常量的值

我不知道Ada,但C等价物为extern const int my_const;,然后在单独的constant.S文件中使用.section .rodata / my_const: .long 0x12345。或者使用自定义部分和链接描述文件将可修改的常量放在二进制文件中特定的位置。

有关Ada与C或asm接口的更多信息,请参阅https://gcc.gnu.org/onlinedocs/gcc-7.3.0/gnat_ugn/Interfacing-to-C.html。它包含从C导入和导出符号定义的示例。(C和asm之间的映射非常简单,因此即使使用asm创建目标文件,也可以告诉Ada它是C的。)

My_Num : Integer;
pragma Import (C, My_Num, "Ada_my_num");

理想情况下,您可以以Ada编译器知道它为常量的方式声明My_Num这将其声明为普通全局(使用Ada_my_num C符号名称进行外部定义。)

这与@ Jose的答案非常类似,只是使用独立的asm来隐藏编译器的值。

您希望编译器能够尽可能地优化。即假设这个"变量"的值在程序的生命周期内不会发生变化,因此可以在函数开始时将其加载到寄存器中,并假设对非内联函数的调用无法对其进行更改。或者为了避免重做涉及它的计算(CSE),所以如果你的源代码有 a * my_const在函数调用之前和之后,它可以将结果保存在寄存器中,而不是从内存中重新加载常量,并在函数调用后重做乘法。

如果编译器认为它是具有未知值的普通全局变量,则不会发生这种情况;它必须假设函数调用可能已经改变了任何全局变量的值。

但是如果您使用普通的全局变量并在Ada编译器可以看到的任何地方为其分配值,那么整个程序链接时优化可以将该值传播到其他位置,或者甚至将其烘焙到其他常量中。 (例如,如果您执行a += 2 * my_constant,则可以在asm输出中的某处对2*my_constant进行硬编码。

(例如,如果你compile+link with -flto让编译器在编译单元之间进行优化。如果GNAT能够以与C前端相同的方式执行此操作,那么IDK​​就可以了,但希望它可以。)

为什么编译器会这样做:因为它当然更有效率!

从内存中加载静态数据的值通常需要多条指令来生成32位地址(在固定的指令宽度ISA上,如SPARC);使用相同数量的指令,您可以直接在寄存器中创建任意32位常量。 ALU指令通常比负载便宜,并且不能在缓存中丢失。

小常量甚至更高效,可以用作addorand或其他任何内容的单个立即操作数。

Constant folding并且内联后的常量传播是程序的asm版本可以比源更少工作的主要方式。例如如果编译器知道5 * my_const的值,则my_const可以在编译时完成。 (因此,如果需要,它可以直接在寄存器中生成 ,而不是加载my_const并使用shift / add。)

某些泛型函数可能会检查if(x>0),但是编译器可能能够证明在函数内联的一个地方总是如此,如果它知道有关常量值的话。 (这是一个价值范围优化)。

拒绝编译器常量的值肯定会使代码效率降低,具体取决于您使用常量的方式。

示例编译器输出。 (我假设您可以编写等效的Ada,其中GNAT' s / gcc的SPARC后端将与clang / LLVM -target sparc类似地进行优化。这一点是为了说明未知值常量与已知常量之间的差异:

From clang6.0 -O3 -fomit-frame-pointer -target sparc on the Godbolt compiler explorer

const int my_const1, my_const2;
static const int my_static = 123;  // a small constant that works as an immediate

int foo(int x) {
    return x + my_static;

}

    retl
    add %o0, 123, %o0         # branch-delay slot


int one_constant(int x) {
    return x + my_const1;
}

    sethi %hi(my_const1), %o1
    ld [%o1+%lo(my_const1)], %o1
    retl
    add %o1, %o0, %o0

后者显然效率低下。似乎clang不知道/ %hi(my_const1)%hi(my_const2)是否相同,因此它为每个静态位置使用另一个sethi。理想情况下,编译器可以在大型函数内使用相同的参考点进行多次访问,但这似乎不是clang / LLVM的情况。 Godbolt没有SPARC gcc,所以我无法轻易尝试。

相关问题