На уровне языка, что такое `ccall`?
Я новичок в Julia, и я пытаюсь понять, на уровне языка, что ccall
. На уровне синтаксиса он выглядит как нормальная функция, но он явно не ведет себя так же, как он принимает свои аргументы:
Обратите внимание, что кортеж типа аргумента должен быть буквальным кортежем, а не переменная или выражение, привязанное к кортежу.
Кроме того, если я оцениваю переменную, связанную с функцией в Julia REPL, я получаю что-то вроде
julia> max
max (generic function with 15 methods)
Но если я попытаюсь сделать то же самое с ccall
:
julia> ccall
ERROR: syntax: invalid "ccall" syntax
Ясно, что ccall
является специальным синтаксисом, но он также не является макросом (префикс @
и недопустимое использование макросов дает более конкретную ошибку). Итак, что это? Это что-то запеченное в языке, или что-то, что я мог бы определить с помощью какой-либо языковой конструкции, с которой я не знаком?
И если это какой-то запеченный кусок синтаксиса, почему было решено использовать нотацию функций, вместо того, чтобы реализовать его как макрос или создать более читаемый и отличный синтаксис?
Ответы
Ответ 1
В текущем ночном (и, следовательно, предстоящем выпуске 0,6) большая часть специального поведения, которое вы наблюдаете была удалена (см. this pull-request). ccall
больше не является зарезервированным словом, поэтому его можно использовать как имя функции или макроса.
Однако все еще есть небольшая странность: допускается функция 3 или 4 аргумента, называемая ccall
, но на самом деле вызов такой функции даст ошибку о ccall argument types
(другие числа аргументов в порядке). Причины идут прямо на ваш вопрос:
Итак, что это? Это что-то запеченное на языке
Да, ccall
, хотя он больше не будет ключевым словом в 0.6, по-прежнему "испечен" на языке несколькими способами:
- Форма выражения
:ccall([four args...])
распознается и специально обработана во время понижения синтаксиса. Этот шаг понижения делает несколько вещей, включая обертывание аргументов при вызове unsafe_convert
, что позволяет настраивать преобразование из объектов Julia в C-совместимые объекты; а также вытягивать аргументы, которые, возможно, должны быть внедрены, чтобы предотвратить сбор мусора ссылочного объекта во время ccall
. (см. вывод code_lowered
или попробуйте функцию expand
, более подробную информацию о компиляторе здесь).
-
ccall
требует расширенной обработки в бэкэнде генерации кода, в том числе: просмотр запрошенного имени функции в указанной разделяемой библиотеке и создание LLVM call
, которая в конечном итоге переводится на машинный код, специфичный для платформы, компилятором LLVM Just-In-Time. (см. различные этапы с code_llvm
и code_native
).
И если это какой-то запеченный кусок синтаксиса, почему было решено использовать функции, но вместо ее реализации в качестве макроса или проектирование более читаемого и четкого синтаксиса?
По причинам, описанным выше, ccall
требует специальной обработки, выглядит ли он как макрос или функция. В этой теме списка рассылки один из создателей Julia (Stefan Karpinski) прокомментировал, почему бы не сделать это макросом:
Я предполагаю, что мы могли бы переопределить его как макрос, но это действительно просто подтолкнуло бы магию дальше.
Что касается "более читаемого и четкого синтаксиса", возможно, это вопрос вкуса. Мне непонятно, почему предпочтительнее другой синтаксис (за исключением удобства синтаксиса синтаксиса синтаксиса LuaJIT/CFFI-стиля, из которых я являюсь поклонником). Мое единственное сильное личное пожелание ccall
состояло бы в том, чтобы иметь аргументы и типы, введенные рядом (например, ccall((:foo, :libbar), Void, (x::Int, y::Float))
), потому что работа с более длинными списками аргументов может быть неудобной. В 0.6 можно будет реализовать эту форму в виде макроса!
Ответ 2
В июле 0.5 и ранее.
Это не функция, и она не является макросом.
Это действительно что-то особенное, запеченное в языке.
Это внутреннее.
В julia 0.6 это изменяется
Это много похоже на макрос, чем на вызов функции.
Но другими способами это не так - он не возвращает АСТ.
Он вызывает функцию, и на достаточно низком уровне он похож на вызов функции julia.
История того, почему она выглядит так, как она есть, выше меня, вам нужно было услышать от одного из людей, которые работали над самым ранним кодом для языка.
Сейчас это повсюду, и это одна из самых трудных вещей, которые нужно изменить, но не невозможно. Это вызвало бы 3-х летние байкинга: -P.
Мне нравится думать о ccall
как о двух вещах.
- Интерфейс внешних функций для C и других скомпилированных языков (например, Fortran, Rust, по-видимому, работают)
- Способ доступа к необработанным кишкам языка "runtime".
Интерфейс внешних функций (FFI)
В большинстве случаев, когда вы используете ccall
в пакете, вы хотите вызвать код, который находится в библиотеке компиляции. В этом смысле это C-Call, например, R-Call или Py-Call.
Я думаю, mlewe/BlossomV.jl - хороший компактный пример.
Для более интенсивного примера oxinabox/SLEEF.jl.
Как FFI, ему не нужно обмениваться памятью/процессом с julia - PyCall.jl does, RCall.jl и Matlab.jl этого не делают.
Это не имеет значения, пока результат вернется.
В этих случаях теоретически возможно заменить ccall
на какой-то safe_ccall
, который будет запускать вызываемую библиотеку в отдельном процессе и не будет segfault julia, если библиотека будет вызвана segfaulted.
Но до сих пор никто не написал такой метод/пакет.
Использование ccall
для FFI выполняется даже в Base, например, для доступа к MPFR для определения BigFloat.
Но это не главная причина, по которой ccall
используется в Base
.
Доступ к кишкам языка.
ccall
действительно то, что заставляет большую часть программы "делать что-то".
Он используется во всех Base, чтобы вызвать функции из src.
Для этого ccall
в основном запускает вызов функции на скомпилированном уровне, который сдвигает указатель инструкции непосредственно в скомпилированный код функции ccall
ed. Как вызов функции, если бы все это было написано в слове C.
Вы можете видеть в base/threadingconstructs.jl ccall, используемый для управления работой с потоками, - который запускает код из src/threading.c.
Используется для отображения раздела диска в память. mmap.jl. - очевидно, не может быть сделано из другого процесса.
Он используется, чтобы сделать раздел кода неподдерживаемый
Используется вызов LibC, чтобы делать что-то вроде malloc
для выделения памяти (хотя сейчас это в основном используется как часть FFI).
Есть трюки, которые вы можете сделать с ccall
до #undef
переменной после того, как она уже была назначена.
ccall
во многих отношениях является ключом мастера к языку.
Заключение
Я описал ccall
здесь как две вещи: функцию FFI и основную часть языка "runtime". Эта двойственность не является реальной, и существует много перекрытий, таких как обработка файлов (это FFI?).
По большому счету многие ожидают, что ccall будет использоваться с использованием FFI.
Здесь ccall может быть просто функцией.
Поведение, которое оно на самом деле имеет, исходит из его использования в качестве основной части языка - ссылки на код julia стандартной библиотеки в Base на код низкого уровня C от src.
Позволяет очень прямое управление запуском процесса julia.