Clang.jlでC言語のヘッダファイルからJuliaラッパーコード自動で生成することができます。
julia> using Clang.Generators
shell> cat c.h
int main(int argc, char **argv);
julia> ctx = create_context("c.h", get_default_args());
julia> build!(ctx)
...
[ Info: Emit Julia expressions...
function main(argc, argv)
ccall((:main, libxxx), Cint, (Cint, Ptr{Ptr{Cchar}}), argc, argv)
end
Clang.jl 0.14が2021/11/17にリリースされています。
このリリースには、以下のような新機能が含まれています。
前方宣言のサポート
今まで、Julia言語的には「前方宣言」というコンセプトがありません。なので、C言語の前方宣言からJuliaに直接転換することができません。
struct Bar { struct Baz *bazptr; }; // 前方宣言
struct Baz { struct Bar *barptr; };
例えば、上記のC言語コードをJuliaに直接翻訳すると、下記のような問題が発生しています。
julia> struct Bar
bazptr::Ptr{Baz}
end
ERROR: UndefVarError: Baz not defined
Stacktrace:
[1] top-level scope
@ REPL[1]:1
julia> struct Baz
barptr::Ptr{Bar}
end
解決方法の一つはopaque
ポインタ型+getproperty
のオーバーローディングです。手動的に書くのがすごく面倒なので、Clang.jlを使うと手軽に適切なコードを生成できます。
julia> build!(ctx)
...
[ Info: Emit Julia expressions...
struct Bar
bazptr::Ptr{Cvoid} # bazptr::Ptr{Baz}
end
function Base.getproperty(x::Bar, f::Symbol)
f === :bazptr && return Ptr{Baz}(getfield(x, f))
return getfield(x, f)
end
struct Baz
barptr::Ptr{Bar}
end
マルチプラットフォームのサポート
プラットフォームによって、あるtype
の定義が違います。例えばmacOSでのtime_t
は、__darwin_time_t
にとって定義されています。Juliaに翻訳すると、Clong
になります。しかし、Windows(x64)の場合は__time64_t
になります。Julia側の対応するtype
はClonglong
です。macOSの場合、Clong
とClonglong
のsize
が同じので、交換するのが問題がないかもしれませんが、念の為、各プラットフォームに対して、コードを生成するのが必要です。
Clang.jl 0.14は、プラットフォームを選択する機能を提供しています。JLLEnvs
というサブモジュールがあります。下記のtriplet
を引数としてget_default_args(triplet)
に指定すると、このtriplet
に対しての環境構築が自動的に行われています。
julia> using Clang
julia> using Clang.JLLEnvs
julia> Clang.JLLEnvs.JLL_ENV_ARTIFACT_TRIPLES
14-element Vector{String}:
"aarch64-apple-darwin20"
"aarch64-linux-gnu"
"aarch64-linux-musl"
"armv7l-linux-gnueabihf"
"armv7l-linux-musleabihf"
"i686-linux-gnu"
"i686-linux-musl"
"i686-w64-mingw32"
"powerpc64le-linux-gnu"
"x86_64-apple-darwin14"
"x86_64-linux-gnu"
"x86_64-linux-musl"
"x86_64-unknown-freebsd12.2"
"x86_64-w64-mingw32"
julia> get_default_args("armv7l-linux-musleabihf")
Downloading [==========> ] 23.0 %%
そうすると、loop
でJuliaバインディングコードの生成が簡単にできます。詳細は以下の通りです。
# X-ref: https://github.com/Gnimuc/LibClang.jl/blob/master/gen/generator.jl
using Clang.Generators
using Clang.JLLEnvs
using Clang_jll
cd(@__DIR__)
include_dir = joinpath(Clang_jll.artifact_dir, "include") |> normpath
clang_dir = joinpath(include_dir, "clang-c")
for target in JLLEnvs.JLL_ENV_TRIPLES
@info "processing $target"
# この`generator.toml`ファイルによる、生成したコードをカスタマイズすることができます。
options = load_options(joinpath(@__DIR__, "generator.toml"))
# for each platform, we generate a new Julia file so we can load it on demand according to `Sys.MACHINE`.(TODO:翻訳)
options["general"]["output_file_path"] = joinpath(@__DIR__, "..", "lib", "$target.jl")
args = get_default_args(target)
push!(args, "-I$include_dir")
headers = detect_headers(clang_dir, args)
ctx = create_context(headers, args, options)
build!(ctx)
end
Doxygenのサポート
Clang.jlの新たなGeneratorは、DoxygenのコメントからJuliaのdocstringに自動で翻訳することができます。こちらのextract_c_comment_style = "doxygen"
を設置すると、このCコード
/**
* Error codes returned by libclang routines.
*
* Zero (\c CXError_Success) is the only error code indicating success. Other
* error codes, including not yet assigned non-zero values, indicate errors.
*/
enum CXErrorCode {
/**
* No error.
*/
CXError_Success = 0,
/**
* A generic error code, no further details are available.
*
* Errors of this kind can get their own specific error codes in future
* libclang versions.
*/
CXError_Failure = 1,
/**
* libclang crashed while performing the requested operation.
*/
CXError_Crashed = 2,
/**
* The function detected that the arguments violate the function
* contract.
*/
CXError_InvalidArguments = 3,
/**
* An AST deserialization error has occurred.
*/
CXError_ASTReadError = 4
};
以下のJuliaコードになります。
"""
CXErrorCode
Error codes returned by libclang routines.
Zero (`CXError_Success`) is the only error code indicating success. Other error codes, including not yet assigned non-zero values, indicate errors.
| Enumerator | Note |
| :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
| CXError\\_Success | No error. |
| CXError\\_Failure | A generic error code, no further details are available. Errors of this kind can get their own specific error codes in future libclang versions. |
| CXError\\_Crashed | libclang crashed while performing the requested operation. |
| CXError\\_InvalidArguments | The function detected that the arguments violate the function contract. |
| CXError\\_ASTReadError | An AST deserialization error has occurred. |
"""
@cenum CXErrorCode::UInt32 begin
CXError_Success = 0
CXError_Failure = 1
CXError_Crashed = 2
CXError_InvalidArguments = 3
CXError_ASTReadError = 4
end
REPLで、ドキュメントがこのような形で表示されています。
help?> Clang.CXErrorCode
CXErrorCode
Error codes returned by libclang routines.
Zero (CXError_Success) is the only error code indicating success. Other error codes, including not yet assigned non-zero values, indicate errors.
Enumerator Note
–––––––––––––––––––––––– –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
CXError_Success No error.
CXError_Failure A generic error code, no further details are available. Errors of this kind can get their own specific error codes in future libclang versions.
CXError_Crashed libclang crashed while performing the requested operation.
CXError_InvalidArguments The function detected that the arguments violate the function contract.
CXError_ASTReadError An AST deserialization error has occurred.
ビットフィールドのサポート
ビットフィールドもサポートされています。
// X-ref: https://en.wikipedia.org/wiki/Bit_field
struct BoxProps
{
unsigned int opaque : 1;
unsigned int fill_color : 3;
unsigned int : 4; // fill to 8 bits
unsigned int show_border : 1;
unsigned int border_color : 3;
unsigned int border_style : 2;
unsigned char : 0; // fill to nearest byte (16 bits)
unsigned char width : 4, // Split a byte into 2 fields of 4 bits
height : 4;
};
julia> build!(ctx)
...
[ Info: Emit Julia expressions...
struct BoxProps
data::NTuple{4, UInt8}
end
function Base.getproperty(x::Ptr{BoxProps}, f::Symbol)
f === :opaque && return (Ptr{Cuint}(x + 0), 0, 1)
f === :fill_color && return (Ptr{Cuint}(x + 0), 1, 3)
f === :show_border && return (Ptr{Cuint}(x + 0), 8, 1)
f === :border_color && return (Ptr{Cuint}(x + 0), 9, 3)
f === :border_style && return (Ptr{Cuint}(x + 0), 12, 2)
f === :width && return (Ptr{Cuchar}(x + 0), 16, 4)
f === :height && return (Ptr{Cuchar}(x + 0), 20, 4)
return getfield(x, f)
end
function Base.getproperty(x::BoxProps, f::Symbol)
r = Ref{BoxProps}(x)
ptr = Base.unsafe_convert(Ptr{BoxProps}, r)
fptr = getproperty(ptr, f)
begin
if fptr isa Ptr
return GC.@preserve(r, unsafe_load(fptr))
else
(baseptr, offset, width) = fptr
ty = eltype(baseptr)
baseptr32 = convert(Ptr{UInt32}, baseptr)
u64 = GC.@preserve(r, unsafe_load(baseptr32))
if offset + width > 32
u64 |= GC.@preserve(r, unsafe_load(baseptr32 + 4)) << 32
end
u64 = u64 >> offset & (1 << width - 1)
return u64 % ty
end
end
end
function Base.setproperty!(x::Ptr{BoxProps}, f::Symbol, v)
fptr = getproperty(x, f)
if fptr isa Ptr
unsafe_store!(getproperty(x, f), v)
else
(baseptr, offset, width) = fptr
baseptr32 = convert(Ptr{UInt32}, baseptr)
u64 = unsafe_load(baseptr32)
straddle = offset + width > 32
if straddle
u64 |= unsafe_load(baseptr32 + 4) << 32
end
mask = 1 << width - 1
u64 &= ~(mask << offset)
u64 |= (unsigned(v) & mask) << offset
unsafe_store!(baseptr32, u64 & typemax(UInt32))
if straddle
unsafe_store!(baseptr32 + 4, u64 >> 32)
end
end
end
最後に
これで、Clang.jl 0.14の新機能についての紹介を終わりました。このパッケージの使い方は、こちらのドキュメントを参考してみてください。