Внутренние элементы OCaml: Исключения

Мне любопытно узнать, как исключения выполняются в OCaml, чтобы сделать их такими легкими. Используют ли они setjmp/longjmp или они возвращают специальное значение в каждой функции и распространяют его?

Мне кажется, что longjmp немного напрягся в системе, но только тогда, когда возникает исключение, при проверке для каждого возвращаемого значения функции нужно будет проверять каждое и каждое значение после вызова функции, что, кажется, я бы поставил много проверок и прыжков, и, похоже, это будет хуже.

Посмотрев, как OCaml взаимодействует с C (http://caml.inria.fr/pub/docs/manual-ocaml/manual032.html#toc142), и, смотря на callback.h, кажется, что исключение помечено с помощью выравнивания памяти объектов (#define Is_exception_result (v) (((v) и 3) == 2)). Это, по-видимому, указывает на то, что его реализация не использует longjmp и проверяет каждый результат функции после каждого вызова функции. Это оно? Или функция C уже пытается поймать какое-либо исключение, а затем преобразует ее в этот формат?

Спасибо!

Ответ 1

Обработка исключений OCaml

Он не использует setjmp/longjmp. Когда a try <expr> with <handle> оценивается, в стек помещается "ловушка", которая содержит информацию об обработчике. Адрес верхней ловушки хранится в регистре¹, а когда вы поднимаете, он переходит непосредственно к этой ловушке, разматывая несколько кадров стека за один раз (это лучше, чем проверка каждого кода возврата). Ловушка также сохраняет адрес предыдущей ловушки, которая восстанавливается в регистре во время рейса.

¹: или глобальный, на архитектурах с недостаточным количеством регистров

Вы можете убедиться сами в коде:

Сравнение с setjmp

Ocaml использует нестандартное соглашение о вызове с небольшим количеством регистров или без них, что делает эту (и хвостовую рекурсию) эффективной. Я полагаю (но я не эксперт), что причина, по которой C longjmp/setjmp не так эффективна на большинстве архитектур. См., Например, эту реализацию x86_64 setjmp, которая выглядит точно так же, как и предыдущий механизм ловушки плюс резервные регистры.

Это учитывается в интерфейсе C/OCaml: обычный способ вызова функции Caml из C-кода, caml_callback, не улавливает OCaml исключения за пределами страны; вы должны использовать конкретный caml_callback_exn, если хотите, который настраивает свой обработчик ловушки и сохраняет/восстанавливает регистры, записанные с помощью вызываемого абонента, из соглашения о вызове C. См. Например. код amd64, который сохраняет регистры и затем переходит к этой метке, чтобы настроить ловушку исключения.