LoginSignup
4
3

More than 1 year has passed since last update.

Clang.jl新機能まとめ

Posted at

Clang.jlでC言語のヘッダファイルからJuliaラッパーコード自動で生成することができます。

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に直接転換することができません。

C
struct Bar { struct Baz *bazptr; };  // 前方宣言
struct Baz { struct Bar *barptr; };

例えば、上記のC言語コードをJuliaに直接翻訳すると、下記のような問題が発生しています。

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
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側の対応するtypeClonglongです。macOSの場合、ClongClonglongsizeが同じので、交換するのが問題がないかもしれませんが、念の為、各プラットフォームに対して、コードを生成するのが必要です。

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バインディングコードの生成が簡単にできます。詳細は以下の通りです。

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コード

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コードになります。

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で、ドキュメントがこのような形で表示されています。

Julia-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.   

ビットフィールドのサポート

ビットフィールドもサポートされています。

C
// 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
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の新機能についての紹介を終わりました。このパッケージの使い方は、こちらのドキュメントを参考してみてください。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3