2012-07-04 10 views
19

He estado tratando de encontrar la convención de llamadas OCaml para poder interpretar manualmente los rastreos de pila que gdb no puede analizar. Desafortunadamente, parece que nunca se ha escrito nada en inglés, excepto por observaciones generales. Por ejemplo, la gente comentará en los blogs que OCaml pasa muchos argumentos en los registros. (Si hay documentación en inglés en alguna parte, un enlace sería muy apreciado.)Convención de llamadas OCaml: ¿es este un resumen preciso?

He intentado descifrarlo de la fuente de ocamlopt. ¿Alguien podría confirmar la precisión de estas conjeturas?

Y, si estoy en lo cierto acerca de los primeros diez argumentos que se pasan en los registros, ¿no es generalmente posible recuperar los argumentos a una llamada a función? En C, los argumentos aún se insertarían en la pila en algún lugar, si solo vuelvo al cuadro correcto. En OCaml, parece que las calles son libres de destruir los argumentos de sus interlocutores.


Registro asignación (de /asmcomp/amd64/proc.ml)

Para poner en funciones OCaml,

  • se pasan Los primeros argumentos 10 enteros y el puntero en los registros Rax, rbx, RDI, RSI , rdx, rcx, r8, r9, r10 y r11
  • Los 10 primeros argumentos de coma flotante se pasan en los registros xmm0 - xmm9
  • Se insertan argumentos adicionales en la pila (leftmost-first-in?), flotadores y ints y punteros entremezclados
  • El puntero trampa (ver excepciones a continuación) se pasa en r14
  • El puntero de asignación (presumiblemente para el montón menor como se describe en esta blog post) se pasa en r15
  • El el valor de retorno se transfiere en rax si es un entero o un puntero, y en xmm0 si es un float
  • ¿Todos los registros guardan la llamada?

Para poner en funciones de C, se utiliza la convención amd64 C estándar:

  • La primera de seis enteros y puntero argumentos se pasan en RDI, RSI, RDX, rcs, r8, y r9
  • los primeros ocho argumentos flotador se pasan en xmm0 - xmm7
  • argumentos adicionales se insertan en la pila
  • el valor de retorno se pasa de nuevo en rax o xmm0
  • Los registros Rbx, RBP, y R12 - R15 son destinatario de la llamada a guardar

Dirección de devolución (de /asmcomp/amd64/emit.mlp)

La dirección de retorno es el primer puntero empujado en el marco de llamada, de acuerdo con amd64 C convención. (Supongo que la instrucción ret asume este diseño.)

excepciones (de /asmcomp/linearize.ml)

El código try (...body...) with (...handler...); (...rest...) consigue linealizado como esto:

Lsetuptrap .body 
(...handler...) 
Lbranch .join 
Llabel .body 
Lpushtrap 
(...body...) 
Lpoptrap 
Llabel .join 
(...rest...) 

y luego se emite en forma de montaje como éste (destinos a la derecha):

call .body 
(...handler...) 
jmp .join 
.body: 
pushq %r14 
movq %rsp, %r14 
(...body...) 
popq %r14 
addq %rsp, 8 
.join: 
(...rest...) 

En algún lugar del cuerpo, hay un código de operación linealizado Lraise que se emite como este montaje exacto:

movq %r14, %rsp 
popq %r14 
ret 

¿Qué es realmente bueno! En lugar de este negocio setjmp/longjmp, creamos un marco ficticio cuya dirección de retorno es el manejador de excepciones y cuyo único local es el marco ficticio anterior. El /asmcomp/amd64/proc.ml tiene un comentario que llama a $ r14 el "puntero de trampa", así que llamaré a este marco falso el marco de trampa. Cuando queremos plantear una excepción, establecemos el puntero de pila en el marco de captura más reciente, establecemos el puntero de trampa en el marco de captura antes de eso y luego "regresamos" al manejador de excepciones. Y apuesto a que si el manejador de excepciones no puede manejar esta excepción, simplemente lo sube.

La excepción está en% eax.

Respuesta

6

¡Esto es más una respuesta que una pregunta! El poco que sé sobre este tema, lo he aprendido al mirar la fuente, al igual que usted, así que no espere que las precisiones sean mucho más autorizadas que su publicación.

Sí, creo que OCaml usa convenciones de llamadas especializadas solo con registros de llamadas guardadas. Una ventaja de esta opción es que simplifica las llamadas de cola: cuando saltas una llamada final, no tienes que derramar o volver a cargar ningún registro.

¹: para llamadas que no son propias, esto solo funciona cuando no hay demasiados argumentos, y por lo tanto no es necesario que se derramen. Si se necesita la asignación de la pila, la llamada se convierte en una llamada sin cola.

Tenga en cuenta que las convenciones de llamada aún dependen en gran medida de la arquitectura de destino. En x86, por ejemplo, un pequeño número de globales se utiliza cuando los registros se agotan y antes de derramar en la pila, para preservar las llamadas de cola.

También estoy de acuerdo en "más a la izquierda, primero en entrar": argumentos son atravesados ​​con el fin de calling_conventions en proc.ml, almacenada con el fin compensado por slot_offset en emit.mlp; se calcularon de derecha a izquierda, pero se devolvieron en orden, en selectgen.ml.

4

Sí, no puede recuperar los argumentos de una llamada, ya que OCaml intenta reutilizar los registros tanto como sea posible, y destruirá su contenido si ya no es útil en el resto de una función. Los depuradores no tienen forma de imprimir los argumentos, solo pueden, en un punto dado de la función, imprimir las variables que todavía están activas, pero para eso, necesitarían modificar ocamlopt para volcar el código DWARF para recuperar los valores.

Cuestiones relacionadas