Clang.jl新機能まとめ

[Clang.jl](https://github.com/JuliaInterop/Clang.jl)でC言語のヘッダファイルからJuliaラッパーコード自動で生成することができます。

```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言語:C
struct Bar { struct Baz *bazptr; };  // 前方宣言
struct Baz { struct Bar *barptr; };
```

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

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

```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に自動で翻訳することができます。[こちら](https://github.com/JuliaInterop/Clang.jl/blob/2c9230dc28a7d6d3bb33025b8c99a302f7ce5170/gen/generator.toml#L127)の`extract_c_comment_style = "doxygen"`を設置すると、このCコード

```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: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: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言語: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
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の新機能についての紹介を終わりました。このパッケージの使い方は、[こちら](https://juliainterop.github.io/Clang.jl/dev/)のドキュメントを参考してみてください。