assembly 是否有任何语言/编译器使用具有非零嵌套级别的x86 ENTER指令?

uttx8gqw  于 2022-11-13  发布在  其他
关注(0)|答案(7)|浏览(163)

那些熟悉x86汇编编程的人非常习惯于典型的函数序言/结语:

push ebp ; Save old frame pointer.
mov  ebp, esp ; Point frame pointer to top-of-stack.
sub  esp, [size of local variables]
...
mov  esp, ebp ; Restore frame pointer and remove stack space for locals.
pop  ebp
ret

同样的代码序列也可以用ENTERLEAVE指令来实现:

enter [size of local variables], 0
...
leave
ret

ENTER指令的第二个操作数是 * 嵌套级别 *,它允许从被调用的函数访问多个父帧。
这在C中没有使用,因为没有嵌套函数;局部变量的作用域仅限于声明它们的函数。这种构造并不存在(尽管有时我希望它存在):

void func_a(void)
{
    int a1 = 7;

    void func_b(void)
    {
        printf("a1 = %d\n", a1);  /* a1 inherited from func_a() */
    }

    func_b();
}

然而,Python * 确实 * 有这样的嵌套函数:

def func_a():
    a1 = 7
    def func_b():
        print 'a1 = %d' % a1      # a1 inherited from func_a()
    func_b()

当然,Python代码不会直接转换成x86机器码,因此无法(不太可能?)利用这条指令。

是否有编译到x86并提供嵌套函数的语言?是否有编译器会发出第二个操作数为非零的ENTER指令?

英特尔在该嵌套级操作数上投入了非零的时间/金钱,基本上我只是好奇是否有人使用它:-)
参考文献:

ohtdti5x

ohtdti5x1#

enter在实践中被避免使用,因为它的性能非常差--请参阅"enter" vs "push ebp; mov ebp, esp; sub esp, imm" and "leave" vs "mov esp, ebp; pop ebp"中的答案。有一堆x86指令已经过时,但出于向后兼容的原因仍然被支持--enter就是其中之一。(leave是可以的,编译器很乐意发出它。
在Python中实现完全通用的嵌套函数实际上是一个比简单地选择几个帧管理指令更有趣的问题--搜索“闭包转换”和“向上/向下funarg问题”,你会发现许多有趣的讨论。
请注意,x86最初是作为Pascal机器设计的,这就是为什么有指令支持嵌套函数(enterleave)、pascal调用约定(被调用方从堆栈中弹出已知数量的参数)(ret K)、边界检查(bound)等等。

zphenhs4

zphenhs42#

正如Iwillnotexist Idonotexist所指出的,GCC * 确实 * 支持C中的嵌套函数,使用的语法与我上面所示的完全相同。
但是,它没有使用ENTER指令。相反,嵌套函数中使用的变量在局部变量区被分组在一起,指向该组的指针被传递给嵌套函数。有趣的是,这个“指向父变量的指针”是通过一个非标准机制传递的:在x64上,它在r10中传递,而在x86(cdecl)上,它在ecx中传递,这是为C++中的this指针保留的(它不支持嵌套函数)。

#include <stdio.h>
void func_a(void)
{
    int a1 = 0x1001;
    int a2=2, a3=3, a4=4;
    int a5 = 0x1005;

    void func_b(int p1, int p2)
    {
        /* Use variables from func_a() */
        printf("a1=%d a5=%d\n", a1, a5);
    }
    func_b(1, 2);
}

int main(void)
{
    func_a();
    return 0;
}

针对64位进行编译时生成以下代码(代码段):

00000000004004dc <func_b.2172>:
  4004dc:   push   rbp
  4004dd:   mov    rbp,rsp
  4004e0:   sub    rsp,0x10
  4004e4:   mov    DWORD PTR [rbp-0x4],edi
  4004e7:   mov    DWORD PTR [rbp-0x8],esi
  4004ea:   mov    rax,r10                    ; ptr to calling function "shared" vars
  4004ed:   mov    ecx,DWORD PTR [rax+0x4]
  4004f0:   mov    eax,DWORD PTR [rax]
  4004f2:   mov    edx,eax
  4004f4:   mov    esi,ecx
  4004f6:   mov    edi,0x400610
  4004fb:   mov    eax,0x0
  400500:   call   4003b0 <printf@plt>
  400505:   leave  
  400506:   ret    

0000000000400507 <func_a>:
  400507:   push   rbp
  400508:   mov    rbp,rsp
  40050b:   sub    rsp,0x20
  40050f:   mov    DWORD PTR [rbp-0x1c],0x1001
  400516:   mov    DWORD PTR [rbp-0x4],0x2
  40051d:   mov    DWORD PTR [rbp-0x8],0x3
  400524:   mov    DWORD PTR [rbp-0xc],0x4
  40052b:   mov    DWORD PTR [rbp-0x20],0x1005
  400532:   lea    rax,[rbp-0x20]              ; Pass a, b to the nested function
  400536:   mov    r10,rax                     ; in r10 !
  400539:   mov    esi,0x2
  40053e:   mov    edi,0x1
  400543:   call   4004dc <func_b.2172>
  400548:   leave  
  400549:   ret

objdump --no-show-raw-insn -d -Mintel的输出
这相当于更详细的内容,如下所示:

struct func_a_ctx
{
    int a1, a5;
};

void func_b(struct func_a_ctx *ctx, int p1, int p2)
{
    /* Use variables from func_a() */
    printf("a1=%d a5=%d\n", ctx->a1, ctx->a5);
}

void func_a(void)
{
    int a2=2, a3=3, a4=4;
    struct func_a_ctx ctx = {
        .a1 = 0x1001,
        .a5 = 0x1005,
    };

    func_b(&ctx, 1, 2);
}
vql8enpb

vql8enpb3#

我们的PARLANSE编译器(用于SMP x86上的细粒度并行程序)具有词法作用域。
PARLANSE试图生成很多很多小的并行计算粒度,然后将它们多路复用到线程上(每个CPU一个线程)。我们不想为每一个颗粒付出“大堆栈”的代价,因为我们有很多颗粒,我们也不想限制任何东西可以递归的深度。由于并行分叉,堆栈实际上是一个仙人掌堆栈。
每个过程在进入时都会构建一个词法显示,以便能够访问周围的词法作用域。我们考虑过使用ENTER指令,但出于两个原因决定不使用它:

  • 正如其他人所指出的,它不是特别快,MOV指令也一样快。
  • 我们观察到,显示通常是稀疏的,在词汇更深的一侧往往是密集的。大多数内部帮助函数只访问它们的直接词汇父函数就可以了;你并不总是需要接触到你所有的父母。有时候一个都不需要。

因此,编译器会准确地计算出函数需要访问的词法作用域,并在函数prolog中生成MOV指令,复制父函数实际需要的显示部分,这通常是1或2对移动。
因此,与使用ENTER相比,我们在性能方面获得了两次优势。
IMHO,ENTER现在是那些遗留的CISC指令之一,这在它被定义的时候似乎是一个好主意,但是被RISC指令序列超越了,甚至Intel x86也优化了。

ma8fv8wu

ma8fv8wu4#

我在使用Simics虚拟平台的Linux引导上做了一些指令计数统计,发现从来没有使用过ENTER。然而,在混合中有相当多的LEAVE指令。CALL和LEAVE之间几乎有1-1的相关性。这似乎证实了ENTER只是慢且昂贵,而LEAVE相当方便的想法。这是在2.6系列内核上测量的。
在4.4系列和3.14系列内核上进行的相同实验显示,LEAVE或ENTER的使用都为零。据推测,用于编译这些内核的较新gcc的gcc代码生成已经停止发出LEAVE(或者机器选项设置不同)。

cygmwpex

cygmwpex5#

IMP 77(由爱丁堡大学开发)允许嵌套例程/函数。英特尔版本的编译器有时使用带有非零级别值的ENTER指令。

h43kikqp

h43kikqp6#

显示IMP源代码和生成的机器代码的相应英特尔指令!主例程/程序%开始0000 C8 00 00 01 ENTER 0000,1

! global variable
%integer sum

! nested routine
%integer %function mcode001( %integer number, x )
 0004 EB 00                                 JMP L1001
 0006                      L1002  EQU $
 0006 C8 00 00 02                           ENTER 0000,2
    ! local variable
    %integer r

    r = number + x
 000A 8B 45 0C                              MOV EAX,[EBP+12]
 000D 03 45 08                              ADD EAX,[EBP+8]
 0010 89 45 F4                              MOV [EBP-12],EAX

    %result = r
 0013 8B 45 F4                              MOV EAX,[EBP-12]
 0016 C9                                    LEAVE
 0017 C3                                    RET
%end
 0018                      L1001  EQU $

! call the nested routine
sum = mcode001(46,24)&255
 0018 6A 2E                                 PUSH 46
 001A 6A 18                                 PUSH 24
 001C E8 00 00                              CALL 'MCODE001' (INTERNAL L1002 )
 001F 83 C4 08                              ADD ESP,8
 0022 25 FF 00 00 00                        AND EAX,255
 0027 89 45 F8                              MOV [EBP-8],EAX

! show the result itos converts binary integer to text
printstring("Result =".itos(sum,3)); newline
 002A FF 75 F8                              PUSH WORD PTR [EBP-8]
 002D 6A 03                                 PUSH 3
 002F 8D 85 F8 FE FF FF                     LEA EAX,[EBP-264]
 0035 50                                    PUSH EAX
 0036 E8 42 00                              CALL 'ITOS' (EXTERN 66)
 0039 83 C4 0C                              ADD ESP,12
 003C 8D 85 F8 FD FF FF                     LEA EAX,[EBP-520]
 0042 50                                    PUSH EAX
 0043 B8 00 00 00 00                        MOV EAX,COT+0
 0048 50                                    PUSH EAX
 0049 68 FF 00 00 00                        PUSH 255
 004E E8 03 00                              CALL '_IMPSTRCPY' (EXTERN 3)
 0051 83 C4 0C                              ADD ESP,12
 0054 8D 85 F8 FD FF FF                     LEA EAX,[EBP-520]
 005A 50                                    PUSH EAX
 005B 8D 85 F8 FE FF FF                     LEA EAX,[EBP-264]
 0061 50                                    PUSH EAX
 0062 68 FF 00 00 00                        PUSH 255
 0067 E8 05 00                              CALL '_IMPSTRCAT' (EXTERN 5)
 006A 83 C4 0C                              ADD ESP,12
 006D 81 EC 00 01 00 00                     SUB ESP,256
 0073 89 E0                                 MOV EAX,ESP
 0075 50                                    PUSH EAX
 0076 8D 85 F8 FD FF FF                     LEA EAX,[EBP-520]
 007C 50                                    PUSH EAX
 007D 68 FF 00 00 00                        PUSH 255
 0082 E8 03 00                              CALL '_IMPSTRCPY' (EXTERN 3)
 0085 83 C4 0C                              ADD ESP,12
 0088 E8 34 00                              CALL 'PRINTSTRING' (EXTERN 52)
 008B 81 C4 00 01 00 00                     ADD ESP,256
 0091 E8 3C 00                              CALL 'NEWLINE' (EXTERN 60)

%endofprogram
 0094 C9                                    LEAVE
 0095 C3                                    RET
      _TEXT  ENDS
      CONST  SEGMENT WORD PUBLIC 'CONST'
 0000                                       db 08,52 ; .R
 0002                                       db 65,73 ; es
 0004                                       db 75,6C ; ul
 0006                                       db 74,20 ; t.
 0008                                       db 3D,00 ; =.
      CONST  ENDS
      _TEXT  SEGMENT WORD PUBLIC 'CODE'
             ENDS
      DATA  SEGMENT WORD PUBLIC 'DATA'
      DATA    ENDS
              ENDS
      _SWTAB  SEGMENT WORD PUBLIC '_SWTAB'
      _SWTAB   ENDS
v7pvogib

v7pvogib7#

! main routine/program
%begin

! global variable
%integer sum

! nested routine
%integer %function mcode001( %integer number, x )
    ! local variable
    %integer r

    r = number + x

    %result = r
%end

! call the nested routine
sum = mcode001(46,24)&255

! show the result itos converts binary integer to text
printstring("Result =".itos(sum,3)); newline

%endofprogram

相关问题