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

Assembly 是否有任何语言/编译器使用嵌套级别为非零的x86 ENTER指令?,assembly,x86,Assembly,X86,熟悉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 同样的代码序列

熟悉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
同样的代码序列也可以通过
ENTER
LEAVE
指令来实现:

enter [size of local variables], 0
...
leave
ret
ENTER
指令的第二个操作数是嵌套级别,它允许从被调用函数访问多个父帧

这在C中没有使用,因为没有嵌套函数;局部变量只有在其中声明的函数的作用域。此构造不存在(尽管有时我希望它存在):

void函数a(void)
{
int a1=7;
无效函数b(无效)
{
printf(“a1=%d\n”,a1);/*a1继承自func_a()*/
}
func_b();
}
但是Python确实有嵌套函数,其行为如下:

def func_a():
a1=7
def func_b():
打印'a1=%d'%a1#a1继承自func_a()
func_b()
当然,Python代码不会直接转换为x86机器代码,因此无法(不太可能?)利用此指令

是否有编译成x86并提供嵌套函数的语言?是否有编译器会发出带有非零秒操作数的
ENTER
指令?

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

参考资料:

GCC确实支持C中的嵌套函数,使用的语法与我上面介绍的完全相同

但是,它不使用
ENTER
指令。相反,嵌套函数中使用的变量在局部变量区域中分组在一起,并将指向该组的指针传递给嵌套函数。有趣的是,这个“指向父变量的指针”是通过非标准机制传递的:在x64中,它被传递在R10中,而在x86(cDeCL)上,它被传递到ECX,它被保留在C++中的<代码>这个< /Cult>指针中(无论如何它不支持嵌套函数)。
#包括
无效函数a(无效)
{
int a1=0x1001;
INTA2=2,a3=3,a4=4;
int a5=0x1005;
无效函数b(int p1,int p2)
{
/*使用func_a()中的变量*/
printf(“a1=%d a5=%d\n”,a1,a5);
}
func_b(1,2);
}
内部主(空)
{
func_a();
返回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  

enter
在实践中被避免,因为它的性能相当差-请参阅中的答案。有许多x86指令已经过时,但由于向后兼容的原因仍然受支持-
enter
就是其中之一。(
leave
是可以的,编译器很乐意发出它。)

在Python中实现完全通用的嵌套函数实际上比简单地选择几个框架管理指令要有趣得多——搜索“闭包转换”和“向上/向下funarg问题”,您会发现许多有趣的讨论

请注意,x86最初设计为Pascal机器,这就是为什么有指令支持嵌套函数(
enter
leave
)、被调用方从堆栈中弹出已知数量的参数的
Pascal
调用约定(
retk
)、边界检查(
bound
)以及,等等这些操作中的许多现在已经过时。

我们的编译器(用于SMP x86上的细粒度并行程序)具有词法作用域

PARLANSE尝试生成许多、许多小的并行计算粒度,然后在线程上多路复用它们(每个CPU 1个)。实际上,堆栈帧是堆分配的;我们不想为每一粒粮食付出“大堆码”的代价,因为我们有很多,我们也不想限制任何东西能重复出现的深度。因为有平行的叉子,所以这个堆栈实际上是一个仙人掌堆栈

每个过程在输入时都构建一个词法显示,以允许访问周围的词法范围。我们考虑使用ENTER指令,但出于两个原因决定不使用它:

  • 正如其他人所指出的,它并不是特别快。MOV指令也同样适用
  • 我们观察到,显示通常是稀疏的,而且在词汇更深的一侧往往更密集。大多数内部助手函数只对其直接词法父函数进行访问;你并不总是需要接触你所有的父母。有时没有
因此,编译器准确地计算出函数需要访问哪些词法作用域,并在函数prolog中生成ENTER将进入的位置,仅生成MOV指令以复制父级显示中实际需要的部分。这通常是一对或两对动作

所以我们在性能上比使用ENTER赢了两次


顺便说一句,ENTER现在是传统的CISC指令之一,在它被定义时似乎是一个好主意,但由于RISC指令序列甚至Intel x86都进行了优化,所以性能优于它。

我使用Simics虚拟平台在Linux引导上做了一些指令计数统计,发现从未使用过ENTER。然而,混合中有相当多的请假指示。打电话和离开之间几乎有1:1的相关性。这似乎证实了这样一种观点,即进入只是缓慢而昂贵,而离开则相当方便。这是在2.6系列内核上测量的


在4.4系列和3.14系列内核上进行的相同实验表明,leve或ENTER的使用率为零。据推测,用于编译这些内核的较新gcc的gcc代码生成已经停止(或者机器选项设置不同)

+1,今天最有趣的问题。对于1),GCC支持