在编译器中实现绑定的函数参数

时间:2012-06-30 05:56:39

标签: assembly compiler-construction programming-languages compilation functional-programming

我对函数式编程语言设计有一个想法,它大量使用绑定函数参数。我试图在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()?

2 个答案:

答案 0 :(得分:1)

进一步想一想,我认为这可以通过使用callee-cleanup约定并手动管理我的所有返回地址来完成。它类似于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]并且执行正确。

失去调用/重击似乎有点激烈,函数add的堆栈框架非常脆弱,所以我将问题至少再打开24小时以期更好溶液


编辑:进一步想一想,你可以通过简单地携带绑定蹦床中的返回地址来保持cdecl,来电清理,呼叫/返回等等(这只需要破坏一个寄存器,或者将它移动到堆栈并返回)。

伪代码:

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调用约定。毫无疑问,这种方法在实施时会出现问题,但我认为它是可行的,而且效率不高。

答案 1 :(得分:0)

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

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