1

我有一个函数式编程语言设计的想法,它大量使用绑定函数参数。我试图将 x86 程序集中的绑定函数参数表示为编译器实现的一部分。

var add  = function(x,y) { return x + y; };
var add2 = add.bind({}, 2);
console.log( add2(3) );      // prints 5

出于互操作性的原因,我想生成裸函数指针,所以我的第一个概念想法是在堆上分配一些可执行内存,并复制到一个存根中,该存根 (a) 推送一个额外的参数并 (b) 调用目标函数。这将是标准库的一部分,并将返回一个本机函数指针,我可以从 x86 汇编程序的其余部分使用它。

我想我遇到了这种方法的一个问题——如果存根用于call获取目标函数,那么堆栈包含一个返回地址,该地址最终被解释为函数参数!如果存根用于jmp到达目标函数,那么调用者和被调用者都不知道在函数返回时如何清理堆栈。

如何解决?我想可以永久保留一个寄存器作为这种行为的标志,但这并不优雅。

bind() 如何在函数式语言的本机编译器中实现?

4

2 回答 2

1

进一步考虑,我认为这可以通过使用被调用者清理约定并手动管理我的所有返回地址来完成。它类似于 stdcall,但不完全相同,因为不能使用 call/ret(?)。

伪代码:

main:
   ; create stub, copy in 0x02 and &add, the resulting function pointer goes in add2
   local add2 = _create_trampoline

   ; make the call
   push [return address]
   push 0x03 ;arg1
   jmp  add2

; the resulting stub, held on the heap somewhere
add2:
   push 0x02 ;bound argument
   jmp  add

; var add(x,y)
add:
   local x = pop
   local y = pop

   eax = x + y;       

   jmp pop

这样,add知道堆栈的布局y x [ptr]和执行正确返回。

失去 call/ret 似乎有点激烈,而且函数的堆栈框架add非常脆弱,所以我将把这个问题至少再开放 24 小时,希望有更好的解决方案。


编辑:进一步考虑,您可以保留 cdecl、caller-cleanup、call/ret 和所有内容,只需在绑定的蹦床中携带返回地址(这只需要破坏一个寄存器,或将其移动到堆栈并返回)。

伪代码:

main:
   ; create stub, copy in 0x02 and &add, the resulting function pointer goes in add2
   local add2 = _magic(0x02, &add);

   ; make the call
   push 0x03;
   call add2;

add2:
   ebx = pop;     ;the return address goes in a temporary
   push 0x02;
   push ebx;
   jmp add

; var add(x,y)
add:
   push ebp;
   mov ebp, esp;

   ; local variables are [ebp+8] and [ebp+12]
   perform calculation into eax

   leave
   ret

在那里,结果是一种非常简洁的技术,可以将绑定函数参数实现为堆上的可执行对象,同时保持 cdecl 调用约定。毫无疑问,这种方法在实施时会出现问题,但我希望它是可行的,而且效率不会太低。

于 2012-06-30T07:25:10.850 回答
0

您不能将预先传递的参数存储在内存中。然后,当您看到“add2”时,您从内存中收集参数,将它们推入堆栈,将其他参数推入堆栈(根据需要),然后像正常一样进行函数调用?

我主要是在大声思考我不知道这是答案,但它看起来对我有用。

于 2012-06-30T06:00:40.823 回答