我对函数式编程语言设计有一个想法,它大量使用绑定函数参数。我试图在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()?
答案 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”时,你从内存中收集参数,将它们推入堆栈,将其他参数推送到堆栈(根据需要),然后像正常一样调用函数调用?
我主要是大声思考我不知道这是答案,但看起来它对我有用。