void[]

D言語にはvoidという関数やメソッドの返り値の型に指定することが出来る値を持たない型があります。void*という何のポインタかは分からないがとりあえずポインタであることを示す型があります。そして、void[]と言う狐につまさたような型があります。 dlang.orgには次のように記述されています。

原文

There is a special type of array which acts as a wildcard that can hold arrays of any kind, declared as void[]. Void arrays are used for low-level operations where some kind of array data is being handled, but the exact type of the array elements are unimportant. The .length of a void array is the length of the data in bytes, rather than the number of elements in its original type. Array indices in indexing and slicing operations are interpreted as byte indices.

void main()
{
    int[] data1 = [1,2,3];
    long[] data2;

    void[] arr = data1;            // OK, int[] implicit converts to void[].
    assert(data1.length == 3);
    assert(arr.length == 12);      // length is implicitly converted to bytes.

    //data1 = arr;                 // Illegal: void[] does not implicitly
                                   // convert to int[].
    int[] data3 = cast(int[]) arr; // OK, can convert with explicit cast.
    data2 = cast(long[]) arr;      // Runtime error: long.sizeof == 8, which
                                   // does not divide arr.length, which is 12
                                   // bytes.
}

Void arrays can also be static if their length is known at compile-time. The length is specified in bytes:

void main()
{
    byte[2] x;
    int[2] y;

    void[2] a = x; // OK, lengths match
    void[2] b = y; // Error: int[2] is 8 bytes long, doesn't fit in 2 bytes.
}

While it may seem that void arrays are just fancy syntax for ubyte, there is a subtle distinction. The garbage collector generally will not scan ubyte arrays for pointers, ubyte being presumed to contain only pure byte data, not pointers. However, it will scan void arrays for pointers, since such an array may have been implicitly converted from an array of pointers or an array of elements that contain pointers. Allocating an array that contains pointers as ubyte[] may run the risk of the GC collecting live memory if these pointers are the only remaining references to their targets.

意訳

これは任意の配列を取るための特殊な型で、void[]と宣言されます。void[]は配列の要素の値が重要でない場合に低レベルな配列の操作をする際に使われます。.lengthプロパティは元の型の配列での.lengthの値ではなく、その配列のbyteでの大きさを返します。void[]でのインデックスアクセスやスライスはbyteの配列に対するそれらの操作として解釈されます。

void main()
{
    int[] data1 = [1,2,3];
    long[] data2;

    void[] arr = data1;            // OK, int[]はvoid[]に暗黙の返還が出来る.
    assert(data1.length == 3);
    assert(arr.length == 12);      // .lengthは暗黙にbyteのものとして扱われる.

    //data1 = arr;                 // 不正な操作 : void[]はint[]に暗黙に変換できない.
                                   // int[]に変換。
    int[] data3 = cast(int[]) arr; // OK, 明示的なint[]への変換は出来る.
    data2 = cast(long[]) arr;      // Runtime error: longのサイズは8バイトであるため、
                                   //12バイトのvoid[]をうまく分割出来ないため 
                                   //ランタイムエラーになる. 
}

void[]コンパイル時にその長さが判明しているならば、void[12]のように静的配列にすることも出来ます。

void main()
{
    byte[2] x;
    int[2] y;

    void[2] a = x; // OK, 同じ長さ.
    void[2] b = y; // Error: int[2]の大きさは8バイトなので2バイトのvoid[2]には変換できない.
}

void[]ubyte[]はちょっとした構文上の違いのようですが、微妙な違いがあります。ubyte[]は純粋なバイト列のデータとみなされるために、GCのスキャンの対象となりません。しかし、void[]はポインタを含む任意の配列から変換された可能性がある為、GCののスキャンの対象となります。つまり、ubyte[]だとそこに何か生きているデータへのポインタが含まれていた場合に、もしそのポインタ以外の参照が無ければGCに回収されてしまうリスクが出来てしまうということです。

問題点

このvoid[]任意の型TにおけるT[]から暗黙に変換できてしまうと言うのが中々の曲者で、場合によっては予想外の動作を引き起こします。

例えば、再帰を使って各要素をそれぞれ2乗するコードです。

import std.stdio;

void main() {
    [1,2,3].square.writeln;
}

auto square(int[] ary) {
    if (ary.length == 0) return [];
    return [ary[0] ^^ 2] ~ square(ary[1..$]);
}

このコードを実行すると僕の環境では[1, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0]と言う結果が得られます。 この関数squareでは2つのreturn文が存在します。まず、[]はvoid[]なので、片方の型はvoid[]です。そしてreturn [ary[0] ^^ 2] ~ square(ary[1..$]);の型はint[] ~ typeof(square([334/+適当+/])の結果となります。int[] -> void[]への暗黙の変換は出来ますが、void[] -> int[]への暗黙の変換は出来ないのでsquareが返す値の型はvoid[]と推論されてしまいintの配列ではなく結果のバイト配列を返します。x86はリトルエンディアンの為、1,2,3はそれぞれ[1,0,0,0],[4,0,0,0],[9,0,0,0]で表されるバイト列に変換されます。

この場合は[]int[]にcastするか、返り値の型をint[]と明示してやれば[1,4,9]と期待通りの結果が得られます。

Fcitxに手こずったArch再インストール

少し早めの大掃除とPCにArch Linuxクリーンインストールしました。 /rootと/varを大きめに取り、swapパーティションを設定しgnomeを入れたまでは良かったのですがfcitxが変な挙動をするようになっていました。

fcitx-gtk3を使うと、アドオンのFcitx XIM Frontendが有効化されていると左下に変換パネルが表示されてしまい、無効化すると変換が確定するまでテキスト入力エリアに表示されない、変換途中で文字を入力し直すなどすると意図してない文字が入力されるなどの症状ができるようになりました。

適当に何度かアドオンの組み合わせを変更してみましたが一向に改善する気配がなかったのでfcitx-gtk3をアンインストールしfcitx-gtk2に切り替えると問題なく動作するようになりました。

なぜgtk3だとダメなんだ...

D言語のオフラインドキュメントのビルド

人権がない(十分に高速なインタネット環境が無い)場合でもD言語のドキュメントが読めるようにビルドしておく方法です。

git clone https://github.com/dlang/dlang.org
cd dlang.org
# LATEST=の部分は適宜バージョンに合わせて読み替えてください。
make -f posix.mak LATEST=2.072.0 docs

実行するとカレントディレクトリ内のwebディレクトリにビルドされたdlang.orgが出来ています。あとはその中身だけ適当なディレクトリに移し替えてcloneしてきたdlang.orgのローカルリポジトリを削除すれば良いだけです。 マルチコアCPUの場合はmakeする際に-jオプションを付けて並列化するとビルドが早くなり嬉しいかもしれません。

修正

make -f posix.mak LATEST=2.072.0 html

ではphobosのドキュメントがビルドされないようです。

D言語でclass同士のopEqualsをオーバーロードする

D言語のclass同士の等価比較演算子オーバーロードは少々特殊です。

structだと

struct S {
    int x;
    bool opEquals(S s) {
        return x == s.x;
    }
}

と書けますがclassの場合だと

class C {
    int x;
    override bool opEquals(Object o) {
        auto c = cast(C)o;
        if (c is null) return false;
        return x == c.x;
    }
}

のように書きます。 D言語ではclassにopEqualsが実装されてる際にはobject.opEqualsの定義に従って比較がされます。 object.opEqualsは以下のように定義されています(実際のソースコードは多少の違いがありますが)

bool opEquals(Object a, Object b)
{
    if (a is b) return true;
    if (a is null || b is null) return false;
    if (typeid(a) == typeid(b)) return a.opEquals(b);
    return a.opEquals(b) && b.opEquals(a);
}

この定義を見れば分かるようにopEqualsには比較対象がなんであってもObjectが渡されるので引数はObjectでなくてはいけません。また、全てのクラスの基底クラスであるObjectには

class Object{
//省略
    bool opEquals(Object o) {
        return this is o;
    }
//省略
}

とopEqualsが実装されているのでopEqualsにはoverride修飾子を付けて定義しなければいけません。

D言語ではObjectから任意のクラスへのキャストが不正なものであった場合は結果がnullになるのでまずnullになっていない(不正な変換ではない)かをチェックします。 そして比較のコードを書くようになります。

immutable class

immutable class IC {
    int x;
    this (int x) {
         this.x = x;
    }
    override bool opEquals(Object o) {
        auto ic = cast(IC)o;
        if (ic is null) return false;
        return x == ic.x;
    }
}

このように定義した場合

Error: function dmatch.core.parser.AST.opEquals does not override any function, did you mean to override 'object.Object.opEquals'?

というエラーメッセージが出ます。原因はimmutable classとした場合immutableが付いているので実際にはbool opEquals (Object o){}でなくなってしまうためobject.Object.opEqualsをオーバーライドすることが出来ないためです。そのため、この場合はclassの定義自体を

class IC {
    immutable int x;
    this (int x) immutable {
         this.x = x;
    }
    override bool opEquals(Object o) {
        auto ic = cast(IC)o;
        if (ic is null) return false;
        return x == ic.x;
    }
}

と変更してやると動くようになります。 僕はこれでハマったのですが lempiji@思秋期 (@lempiji) | Twitterさんにこの解決法を教えていただき解決することが出来ました。ありがとうございました。

LinuxでCPUの一つが常時使用率100%になる問題

一部のハードウェアにおいて、kworkerが一つのスレッドをほぼ専有してしまうことがLinuxでは結構前から度々起きているようです。症状も様々で、有線LANを使うと発生すると言うもの、動画編集をすると発生すると言うもの、何もしなくても発生すると言うものなど様々なものがあるようです。 対症療法的でしかないのですが、一応解決策はあるのでメモしておきます。

grep . -r /sys/firmware/acpi/interrupts/

この中でenabledであり値が異様に高いものがあると思います。 それをrootで、

echo disable > /sys/firmware/acpi/interrupts/(先ほど見つけた値が異様に高いファイル) 

とするとCPU利用率が低下します。

自動化

ただこれを毎回起動するたびにやるのは面倒なので自動化します。

#!/usr/bin/zsh
echo disable > /sys/firmware/acpi/interrupts/(先ほど見つけた値が異様に高いファイル) 

などのような内容のシェルスクリプトを書き、chmodで実行可能にして/usr/local/binなどのパスが通っているディレクトリに配置します。

systemdの場合

[Unit]
Description = disable /sys/firmware/acpi/intterupts/gpe13

[Service]
#書いて配置したスクリプトに合わせてパスを変える
ExecStart = /usr/local/bin/disableGPE13
Type = simple
[Install]
#多分multi-user.targetで無くてもいい
WantedBy = multi-user.target

のようなserviceファイルを書いてdisableGPE.serviceなどの名前で/etc/systemd/systemディレクトリに配置したあと

systemctl daemon-reload
systemctl enable disableGPE

とroot権限でコマンドを打てば次回起動時から自動で実行してくれます。

upstartの場合

/etc/rc.localに

#gpe13は例
echo disable > /sys/firmware/acpi/interrupts/gpe13

などとexit 0の前に追記するだけで次回起動時から自動で実行してくれます。

高専カンファ行ってきました

高専カンファという高専生などによる勉強会に行ってきました。僕は今までこの手のイベントに参加したことが無かったのですが、香川から比較的近い明石で開催されるということもあり参加させていただきました。 LTが予想以上に面白く5分間という時間がとても短く感じられました。 同じロボコニストの方とも知り合え、互いの活動についての情報交換もでき、良い影響を与えられました。 近いうちに発表者として高専カンファに参加できるよう技術を高めていきたいです。

Arch Linuxをインストールした

今まではLinux Mintを使っていたのですが、pacmanが使いたくなってArch Linuxに変えました。 Windows10,Ubuntu,Arch Linuxのマルチブートにしましたが、予想よりは簡単でした。 ddでUSBにisoを焼く際に間違えてHDDに焼いてしまい構築していたWindows環境とUbuntu環境をぶち壊すとかありましたがインストールは思ったよりは苦労しませんでした。 今まで使ってきたLinux Mintと違い、かなり自分でやらなくてはいけない部分が増えましたが結構楽しいです。 dmdやdubもpacmanで取ってこれますし、かなり楽で嬉しいです。