Внутренние компоненты OCaml: исключения
Мне любопытно узнать, как обрабатываются исключения во время выполнения OCaml, чтобы сделать их такими легкими. Используют ли они setjmp / longjmp или возвращают специальное значение в каждой функции и распространяют его?
Мне кажется, что longjmp будет немного напрягать систему, но только при возникновении исключения, при проверке каждого возвращаемого значения функции потребуется проверять каждое значение после вызова функции, что, как мне кажется, приведет к много проверок и прыжков, и, кажется, это будет работать хуже всего.
Если посмотреть, как OCaml взаимодействует с C ( http://caml.inria.fr/pub/docs/manual-ocaml/manual032.html), и посмотреть на callback.h, то кажется, что исключение помечено с помощью выравнивание объектов в памяти ( #define Is_exception_result(v) (((v) & 3) == 2)). Кажется, это указывает на то, что его реализация не использует longjmp и проверяет каждый результат функции после каждого вызова функции. Это оно? Или функция C уже пытается перехватить любое исключение, а затем преобразовать его в этот формат?
Спасибо!
1 ответ
Обработка исключений OCaml
Не использует setjmp/longjmp
, Когда try <expr> with <handle>
оценивается, в стек помещается "ловушка", которая содержит информацию об обработчике. Адрес самой верхней ловушки хранится в регистре¹, и когда вы повышаете, он сразу переходит к этой ловушке, разматывая несколько кадров стека за один раз (это лучше, чем проверка каждого кода возврата). Ловушка также хранит адрес предыдущей ловушки, которая восстанавливается в регистре во время вызова.
¹: или глобальный, на архитектурах с недостаточным количеством регистров
Вы можете сами убедиться в коде:
- компиляция байт-кода: строки 635-641, две
Kpushtrap/Kpoptrap
байт-коды окружаютtry..with
выражение - нативная компиляция: строки 254-260, снова инструкции
Lpushtrap/Lpoptrap
вокруг выражения - выполнение байт-кода для байт-кода
PUSHTRAP
(помещает ловушку / обработчик),POPTRAP
(удалите его, без ошибок) иRAISE
(прыгнуть в ловушку) - эмиссия нативного кода на mips и на amd64 (например)
В сравнении с setjmp
Ocaml использует нестандартное соглашение о вызовах с несколькими или без регистров, сохраненных вызываемыми, что делает это (и хвостовую рекурсию) эффективным. Я полагаю (но я не эксперт) это причина, почему C longjmp/setjmp
не так эффективен на большинстве архитектур. Посмотрите, к примеру, эту реализацию x86_64 setjmp, которая выглядит точно так же, как предыдущий механизм перехвата плюс сохранение регистров вызываемого абонента.
Это учитывается в интерфейсе C/OCaml: обычный способ вызова функции Caml из кода C, caml_callback
, не ловит исключения OCaml-land; Вы должны использовать определенный caml_callback_exn
если хотите, он устанавливает свой обработчик ловушек и сохраняет / восстанавливает регистры, сохраненные вызываемым абонентом, соглашения о вызовах C. См. Например код amd64, который сохраняет регистры, затем переходит к этой метке, чтобы настроить ловушку исключения.