ventajas desventajas caracteristicas ocaml

desventajas - Convención de llamadas de OCaml: ¿es este un resumen preciso?



ocaml ventajas y desventajas (2)

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

Sí, creo que OCaml usa convenciones de llamadas especializadas solo con registros de guardado de llamadas. Una ventaja de esta elección es que simplifica las llamadas de cola: cuando salta a través de una llamada de cola tail, no tiene que derramar o recargar ningún registro.

¹: para las llamadas no automáticas, esto solo funciona cuando no hay demasiados argumentos y, por lo tanto, no es necesario que se derrame. Si la asignación de pila es necesaria, la llamada se convierte en una llamada no de cola.

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

También estoy de acuerdo en "left-most-first-in": los argumentos se recorren en orden calling_conventions a las proc.ml en proc.ml , almacenadas en orden de desplazamiento por slot_offset en emit.mlp ; se calcularon de derecha a izquierda, pero se devolvieron en orden, en selectgen.ml .

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 nada se ha escrito en inglés, excepto las 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 algún lugar, se agradecería mucho un enlace).

Así que he estado tratando de descifrarlo de la fuente ocamlopt. ¿Alguien podría confirmar la exactitud de estas conjeturas?

Y, si estoy en lo cierto acerca de los primeros diez argumentos que se pasan en los registros, ¿no es posible, en general, recuperar los argumentos de una llamada de función? En C, los argumentos aún serían empujados hacia la pila en algún lugar, si tan solo volviera al marco correcto. En OCaml, parece que las personas que llaman tienen la libertad de destruir los argumentos de sus interlocutores.

Asignación de registro (de /asmcomp/amd64/proc.ml )

Para llamar a las funciones de OCaml,

  • Los primeros 10 argumentos de enteros y punteros se pasan en los registros rax, rbx, rdi, rsi, rdx, rcx, r8, r9, r10 y r11
  • Los primeros 10 argumentos de punto flotante se pasan en los registros xmm0 - xmm9
  • Los argumentos adicionales se insertan en la pila (¿de la izquierda, primero en entrar ? ), Los puntos flotantes y los ints y los punteros se entremezclan
  • El puntero de captura (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 publicación de blog ) se pasa en r15
  • El valor de retorno se devuelve en rax si es un entero o puntero, y en xmm0 si es un flotador
  • ¿Todos los registros son para guardar llamadas?

Para llamar a las funciones de C, se usa la convención estándar de amd64 C:

  • Los primeros seis argumentos de enteros y punteros se pasan en rdi, rsi, rdx, rcs, r8 y r9
  • Los primeros ocho argumentos flotantes se pasan en xmm0 - xmm7
  • Argumentos adicionales son empujados en la pila
  • El valor de retorno se devuelve en rax o xmm0
  • Los registros rbx, rbp y r12 - r15 están guardados por el usuario

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

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

Excepciones (de /asmcomp/linearize.ml )

El código try (...body...) with (...handler...); (...rest...) try (...body...) with (...handler...); (...rest...) se linealiza así:

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

y luego emitido como un conjunto como este (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 Lraise que se emite como este ensamblaje exacto:

movq %r14, %rsp popq %r14 ret

Lo que es realmente genial! En lugar de este negocio setjmp / longjmp, creamos un marco ficticio cuya dirección de retorno es el controlador 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 captura", así que llamaré a este marco ficticio el marco de captura. Cuando queremos generar una excepción, establecemos el puntero de pila en el cuadro de captura más reciente, establecemos el puntero de captura en el cuadro de captura antes de eso, y luego "regresamos" al controlador de excepciones. Y apuesto a que si el controlador de excepciones no puede manejar esta excepción, simplemente la vuelve a subir.

La excepción está en% eax.


Sí, no puede recuperar los argumentos de una llamada, ya que OCaml intenta reutilizar los registros tanto como sea posible, y así 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 aún están activas, pero para eso, necesitaría modificar ocamlopt para volcar el código DWARF para recuperar los valores.