憂鬱な午後のひととき


10月30日_

LLVM

久しぶりにLLVMでコードを書いてみる. LLVMならx86/x64/armに対応できるかなと思ったけどVisual Studio用x64コードは生成できないのか. 残念. VCだけ別に書く? どうしよう. とりあえずp = (1 << 192) - (1 << 64) - 1のときの384bit→192bit剰余算を書いてみる.

384bitのxをx = [H:L]とする. H, Lはそれぞれ192bit.
H = [H2:H1:H0]とする. Hiは64bit.
(1 << 192) % p = (1 << 64) + 1だから
mod pで
    x = L + (H << 192)
      = L + H + (H << 64)
      = L + H + [H2:H1:H0:0]
      = L + H + [H1:H0:0] + (H2 << 192)
      = L + H + [H1:H0:0] + H2 + (H2 << 64)

[e:x] = L + H + [H1:H0:H2] + [H2:0] ; 2bit(e) over
      = x + e + (e << 64)

あってるのかな, これ. とりあえず昔自分が書いたllvm入門を眺めながら試してみる. carry操作を避けるためにLLVMが持つ192bit整数などにどっぷり依存する戦略. 上の式で一時的に192 + 2bitまで増えるので変数をi194にゼロ拡張する.

define void @modp192(i64* %px) {
entry:
    ; pxを192bit整数へのポインタにキャスト

    %pL = bitcast i64* %px to i192*

    ; &px[3]を192bit整数へのポインタにキャスト

    %pH64 = getelementptr i64* %px, i64 3
    %pH = bitcast i64* %pH64 to i192*

    ; LやHを192bit整数として読み込む

    %L192 = load i192* %pL
    %H192 = load i192* %pH

    ; その後194bit整数にゼロ拡張する

    %L = zext i192 %L192 to i194
    %H = zext i192 %H192 to i194

    ; t1 = L + H

    %t1 = add i194 %L, %H ; %t1 = L + H

    %px3 = getelementptr i64* %px, i64 3
    %px4 = getelementptr i64* %px, i64 4
    %px5 = getelementptr i64* %px, i64 5

    ; 64bit整数H0, H1, H2を読み込む

    %H0_ = load i64* %px3
    %H1_ = load i64* %px4
    %H2_ = load i64* %px5

    ; 194bit整数にゼロ拡張して

    %H0 = zext i64 %H0_ to i194
    %H1 = zext i64 %H1_ to i194
    %H2 = zext i64 %H2_ to i194
    %H0_1 = shl i194 %H0, 64
    %H1_1 = shl i194 %H1, 128
    %H10 = or i194 %H1_1, %H0_1

    ; H102 = [H1:H0:H2]を作る

    %H102 = or i194 %H10, %H2 ; [H1:H0:H2]

    ; %t2 = L + H + [H1:H0:H2]

    %t2 = add i194 %t1, %H102

    ; (H2 << 64)を作ってt2に加える

    %H2_1 = shl i194 %H2, 64

    ; %t3 = L + H + [H1:H0:H2] + [H2:0]

    %t3 = add i194 %t2, %H2_1

    ; t3の上位2bitを取り出すために192bit右シフトする

    %e = lshr i194 %t3, 192

    ; それを64bit左シフトして足す

    %e64 = shl i194 %e, 64
    %t4 = add i194 %t3, %e
    %t5 = add i194 %t4, %e64 ; t3 + e + (e << 64)

    ; t5は実質192bitになったので194bitから192bitにキャストする

    %t6 = trunc i194 %t5 to i192

    ; 元の場所にかき込む

    store i192 %t6, i192* %pL

    ret void
}

結構長くなってしまった. やりたいことに対して型情報を冗長に書かないといけないのが苦痛. AやBがi192で定義してるのだからor i192 A, Bでなくてor A, Bでいいだろうみたいな. llcでネイティブコードを生成する.

; llc-3.5 -o - modp.ll -x86-asm-syntax=intel
modp192:
    mov     rax, qword ptr [rdi + 8]   ; L1
    mov     rcx, qword ptr [rdi + 16]  ; L2
    mov     r8, qword ptr [rdi + 24]   ; H0
    mov     rsi, qword ptr [rdi]       ; L0
    add     rsi, r8
    mov     r9, qword ptr [rdi + 32]   ; H1
    adc     rax, r9
    mov     r10, qword ptr [rdi + 40]  ; H2
    adc     rcx, r10
    sbb     rdx, rdx
    and     rdx, 1
    add     rsi, r10
    adc     rax, r8
    adc     rcx, r9
    adc     rdx, 0
    add     rax, r10
    adc     rcx, 0
    adc     rdx, 0
    and     rdx, 3 ; ***
    add     rsi, rdx
    adc     rax, 0
    adc     rcx, 0
    add     rax, rdx
    adc     rcx, 0
    mov     qword ptr [rdi], rsi
    mov     qword ptr [rdi + 8], rax
    mov     qword ptr [rdi + 16], rcx
    ret

おお, かなり優秀だ. 64bitシフトなどは全てレジスタ移動ですませている(そうなっていることを期待してコードを書いた). ただよく見ると***の部分が冗長だな. 194bitじゃなくて256bitにすればいいのかな.

→ そうだった. i194をi256に変えれば***は無くなった. 理想的なコード生成だ.

試しに-march=x86を付けてみると4-byte Spillや4-byte Folded Reloadが結構入った. これが絶対必要なのかうまくやればいいのかは調べていない. -march=aarch64だと

modpP192:
    ldr      x1, [x0]
    ldr     x2, [x0, #8]
    ldr     x3, [x0, #16]
    ldr     x4, [x0, #24]
    ldr     x5, [x0, #32]
    ldr     x6, [x0, #40]
    adds     x1, x1, x4
    adcs    x2, x2, x5
    adcs    x3, x3, x6
    adcs    x7, xzr, xzr
    adds     x1, x1, x6
    adcs    x2, x2, x4
    adcs    x3, x3, x5
    adcs    x4, x7, xzr
    adds     x2, x2, x6
    adcs    x3, x3, xzr
    adcs    x4, x4, xzr
    adds     x1, x1, x4
    adcs    x2, x2, xzr
    adcs    x3, x3, xzr
    adds     x2, x2, x4
    adcs    x3, x3, xzr
    str      x1, [x0]
    str     x2, [x0, #8]
    str     x3, [x0, #16]
    ret

ARM64のことは知らないけどx64にほぼ対応して最小のadd回数なので問題なさそう. -march=armだとx86ほどではないけどSpillやReloadが発生した. x64やarmのタイトなコード生成から想像するとそれらは必要なものなのかな.

ここまでくるとVCのx64 asmコードを生成しないのが残念だなあ. 対応させるのは難しいんだろうか.

10月29日_

ランニング

流石に朝5~6時台はTシャツだとさぶくなってきた.

10月28日_

『デバッガによるx86プログラム解析入門【x64対応版】』(うさぴょん)

たいしてお役に立ってないのに謝辞に記載していただいてとても恐縮. 個人的には6章の「ゼロからはじめるデバッガ自作」が興味深かった. 以前, DLLインジェクションと自己書換え系の処理をしたときに, それ系のAPIをいくつか調べながら使ったことがある. 手さぐりだったのでなんだかよく分からないコードになってしまった. それらが一歩一歩説明されているのはよい案内図になる.

ただ私の作った奴は64bit版に対応させようとしていろいろはまった. 今回のはx64対応版とあるので, そのあたりの話がもう少しつっこんであるとよかったかもしれない.

あと, 単なる技術的な解説だけでなくそれをとりまく社会や法律についても言及されているのが興味深かった.

10月26日_

『ある日、アヒルバス』(山本幸久)

高卒入社5年目のバスツアーガイドの人が主人公. 研修をする側のてんぱい具合とか, 吹っ切れる人とか, 男社会に対して革命を目指そうと思ってる人とか. みながんばっててよい.

終盤, 思わぬところで社歌を歌うシーンはぐっとくる. やや気の抜けた社歌なのがより効果的だ.

10月25日_

『PSYCHO-PASS サイコパス』

アニメ. 2が始まったらしいが私がみたのは1の新編集版. 大分遅れてるな. 犯罪をしそうな精神を持った人が完全に管理された社会. 後半の首謀者マキシマの行動がすごいな. 『MONSTER』のヨハンみたいだ. ラストのヒロインの選択が現実路線なのも, 単純な勧善懲悪でないのがいい.

10月24日_

debug debug

ひたすらtestとdebug. test中にエラーが見つかって, その原因を探しているうちに別のエラーが見つかる. これはなんだろうと調べたらつい最近本家Linuxのバグだと分かる. そのパッチを取り込んでテストしていたらまた別のエラー(今度はこちらの責任)が見つかって.

あれ, 今なんのエラーを見てたのだっけみたいな. 自分がメインで手げがけたものは殆どsmall startの「確かに大丈夫」なものを組み合わせるスタイルだったが, こういうある程度の規模になってしまったのものをちゃんとするというのはなかなか大変.

10月21日_

charに対するnew-placement

バッファ操作のためにnew-placementを使うことがあって

for (size_t i = 0; i < n; i++) {
    new(&p[i]) T();
}

みたいなコードを書いていた. T = charのときはp[i] = 0となるので単なるmemsetに置き換えられるだろうと思ったけどそうならない.

new(p) char();はif (p) *p = 0;という挙動をするようだ. ループの外でpがNULLになるかをチェックしてくれてもいいように思うが, VC, gcc, clangどれもループ内で毎回チェックしていた.

10月18日_

_addcarry_u64続き

残念ながら今のVCはintrinsicにたいては保守的な最適化しか効かないので

template<size_t N>
void add(uint64_t *z, const uint64_t *x, const uint64_t *y)
{
    unsigned char c = _addcarry_u64(0, x[0], y[0], &z[0]);
    for (size_t i = 1; i < N; i++) {
        c = _addcarry_u64(c, x[i], y[i], &z[i]);
    }
}

みたいな固定長ループを展開してくれないのだった. どうしよう.

10月17日_

_addcarry_u64

おお, ちょっと見ない間にVCで使えるIntrinsicにこんなものが. carryつきaddじゃないか. Intelの人にも入れてくれって言ってみたことあるやつだ. sbbに相当する_subborrow_u64やHaswellのmulxに相当する_mulx_u64もあるぞ. 試しに

void add(uint64_t *z, const uint64_t *x, const uint64_t *y)
{
    unsigned char c = _addcarry_u64(0, x[0], y[0], &z[0]);
    c = _addcarry_u64(c, x[1], y[1], &z[1]);
    c = _addcarry_u64(c, x[2], y[2], &z[2]);
    _addcarry_u64(c, x[3], y[3], &z[3]);
}

をコンパイルしてみる.

add proc:
  00000    mov     rax, [rdx]
  00003    add     rax, [r8]
  00006    mov     [rcx], rax
  00009    mov     rax, [rdx+8]
  0000d    adc     rax, [r8+8]
  00011    mov     [rcx+8], rax
  00015    mov     rax, [rdx+16]
  00019    adc     rax, [r8+16]
  0001d    mov     [rcx+16], rax
  00021    mov     rax, [rdx+24]
  00025    adc     rax, [r8+24]
  00029    mov     [rcx+24], rax

素晴らしい.

10月16日_

『論理爆弾』(有栖川有栖)

「しらみつぶしの時計」みたいなパズルっぽいのを想像していたら全く違った. 終盤のヒロインのうっかりミスと危機的状況の盛り上がりはよかったが肩すかしを食らった感もある. このペースで進めて肝心の話にけりがつくのかなあ.

10月15日_

munmap

munmapは引数にmmapされたポインタだけでなくサイズも必要. Xbyak::Allocatorのfreeはポインタしか持ってないので munmapを使うMmapAllocatorはそのポインタを内部で管理するはめになってしまった.

カスタムアロケータなんて使ってる人はいないだろうから仕様を変更してしまおうかな. どうしよう.

GMPをforkしたMPIRのカスタムアロケータのfreeはサイズを受け付けるようになっていて, 最初からそうすべきだったかと悔やまれる.

10月14日_

必要のない機能や必要のないクラスは作らない(自戒も込めて).

当たり前に思えるのだけど大規模なものになってくると様々な理由であったりする. 一度入ってしまうと削りにくいことを考えると追加するときにより慎重に考える必要がある.

とか思っていたら, 新人研修やOJTで「設計書通りのものを作らせる」という練習はよくやってるけど, 「その指示されたものは正しいのか, 必要なのか」と問わせる練習というのはしてなかった気がする.

自分に照らせばベンチャーでの受託開発なんかが一番その訓練に該当するだろうか. リソースが足りないのだから最小限の工数で先方が一番納得できるところに落とし込む必要がある.

ほんのちょっとの制約を入れればコードやロジックがすっきりするならどんどん提案して受け入れられればそれを選択する. そうすればバグも出にくいし, メンテナンスもし易い.

研修で考えると立場上上司や先輩からの指示だからそれにたいしてどうこういうのはかなりハードルが高いし難しい. いくつかサンプルを出して考えてみようという練習をするとよいかもしれない.

10月13日_

MmapAllocator

mmap版を用意してみた. ってブランチにする必要はなかったか.

posix_memalignとmmapの違いで気になるのが一つあった. posix_memalignが返すポインタは(それほどメモリをとってないときは)2GiBに収まってるけどmmapのは63bit目が立ってる. コード領域とXbyakの実行領域が2GiBに入ってることを仮定したオフセットの利用をしていると困るかもしれない(Windowsはその仮定は成り立ってないけど).

10月11日_

mmap + mprotect

10日のコードはAlignedMalloc(posix_memalignのラッパー)ではなくmmapされた領域だとエラーにならないらしい(mprotect_test.cpp). mmapを使うようにした場合, 何か後方互換性を壊すことはあるのかな. そもそも数万個もJITコードを生成するのはレアケースだからカスタムアロケータで逃げてもらうか. どうしよう.

10月10日_

mprotectがこける

Xbyakのissue. へえmprotectはmap countというのを消費するのか. 全然知らなかった.

#define XBYAK_NO_OP_NAMES
#include "xbyak/xbyak.h"

int main()
{
    for (int i = 0; i < 70000; i++) {
        void *p = Xbyak::AlignedMalloc(4096, 4096);
        printf("i=%d, p=%p\n", i, p);
        if (!Xbyak::CodeArray::protect(p, 4096, true)) {
            puts("ERR");
            return 1;
        }
    }
}

確かにこのコードを走らせると32768回ループする前にエラーで落ちた. どうやって対応しようかな. というか3万回以上のJITコード生成って….

ループ内でメモリは解放しなくてもprotect(p, 4096, false)すれば落ちないのか. ということは同時に3万個以上のmprotectされた領域を管理できないということか.

10月9日_

『カイシャデイズ』(山本幸久)

大きくはない内装業者の社員それぞれに焦点を当てた連作短編集. やな奴や, こんな人近くにいたら困るだろというような人たちもいるけど憎めない. 統括室長の隠れた働きを描く「いつもおひとり様」や妙に自信過剰な「ガウディでよろしく」が好み. 「ショーカツデイズ」もラストで本気になるのがいい. この著者の本は初めて. 他のも読んでみよう.

10月8日_

C99のdesignated initializerとC++11のinitializer list

C99のdesignated initializerはあくまで記法の省略なので構造体の順に初期化されるようだ. しかしC++のinitializer listは左から現れた順に評価される. それなら

#include <stdio.h>

struct A {
    A(int x) { printf("A %d\n", x); }
};
struct B {
    B(int x) { printf("B %d\n", x); }
};
struct C {
    C(int x) { printf("C %d\n", x); }
};

struct D {
    A a;
    B b;
    C c;
};

int f(int x)
{
    printf("f %d\n", x);
    return x;
}
int main()
{
    D d = { .b = f(3), .c = f(2), .a = f(1) };
}

というコードを書いたら"f 3", "f 2", "f 1"という順と"A 1", "B 3", "C 2"という順で(この二つがどう混ざるかは不明だが)現れると思った. で試すとclang 3.4でbuildしたらclangがクラッシュした. clang 3.5では警告はでないでbuildされたけど実行しても何も表示されない. 何も表示されないというのは意外だ. バグなのかコードが悪いのか(副作用のあるもの書いちゃ駄目なんだっけ)かわからんけど混ぜるな危険だということはよく分かった.

10月7日_

sorry, unimplemented: non-trivial designated initializers not supported

同僚が書いていたコードで発生した. 最小化したのは次のコード.

struct A {
    int x;
    int y;
};

int main()
{
    struct A a = {
        .y = 0
    };
}

レビューとしてはC++にC99の機能を入れちゃいかんで終わりなんだがg++は表題のエラーになるけどclangは通す(ので気がついてなかった). C99の機能は普段使わないので知らなかったけどこの初期化順序は構造体の宣言順序なんやね.

struct A {
    int a;
    int b;
    int c;
};

に対して

struct A x = { .b = 2, .a = 1, .c = 3 };
struct A x = { .a = 1, .c = 3, .b = 2 };

はどちらもA x = { 1, 2, 3 };と書いたのと同じ. 省略されたときとか, 途中で番号にしたときとかいろいろややこしいルールがあるんだなあ.

10月6日_

cppts filesystem

次期C++に向けての仕様策定中のFilesystem Library Proposalのメモ. Bootとの一番の違いはpathクラスの変換メソッドがUTF-8への変換を直接サポートするところ.

string string() const;
wstring wstring() const;
string u8string() const;
u16string u16string() const;
u32string u32string() const;

こうやってみるとやはりu8stringという型がないのが気持ち悪い. permissionまわりはPOSIXべったりなのでWindowsには不利かな. VCはVisual Studio 2012から既にある程度filesystemが実装されている. ただまだまだ変更が入る可能性は高いので注意.

10月5日_

『真夜中の探偵』(有栖川有栖)

第2次世界大戦終了時から微妙に分岐したパラレルワールド. この世界では私的探偵行為が禁止されている. 国家の秩序を脅かすため. 隠れて探偵をしていた17歳の少女の父親も逮捕されてしまう. なんとも暗示の多い世界だ. ヒロインはまだ何もできない. 探偵っぽいことをしてはたしなめられたり怒られたりする.

探偵が探偵らしく振る舞えないミステリってすごい設定だ. 今は伏線はりまくりで今後どうなっていくのか.

小説内で登場人物が過呼吸になったときペーパーバック法(使ったのは鞄だったが)で落ち着かせていた. 今はそれはしてはいけないとされているらしい(過換気症候群にペーパーバック法はいけません). ためしてガッテンでもやっていた(窒息死を招く大誤解! 本当は怖い過呼吸SP).

10月4日_

K&Rスタイルで納得できないこと via ときどきの雑記帳

C++でもメソッドで改行するかどうするかちょっと迷うときはあるけど理由なんて考えたことなかったな(今はほぼ改行に統一). 理由を推測するならまさにK&Rスタイルだったからなんじゃないかと. つまり引数をとる関数は

// K&R
int main(argc, argv)
    int argc;
    char *argv[];
{
    ...
}

という形だった. argvのところで改行するのは自然. だったら引数が無い関数も改行するだろう. ここで関数の定義方法がANSIスタイルに変わっても改行方法は従来の形式を踏襲した.

// ANSI
int main(int argc, char *argv[])
{
    ...
}

本当かどうかは知らない.

10月3日_

アルドノア・ゼロ

録画だけしていたアニメ. 見始めたら面白くて最後まで見てしまった. 主人公の冷静沈着で臨機応変, 卓越した洞察力がすごい. 緊迫感もよい. ラストはどうなったんだろう. みな生きてるのだろうか.

10月1日_

モデルと実装

1年近く前からWalBの開発に関わってる. 当初漠然と思っていたよりずっと難しい. 何が難しいのか一言でいえないのがもどかしい程度に難しい.

最近はWalBが使ってるlogとdiffと呼ばれるパーツたちが正しく動くことを示すためにきちんとしたモデルの再定義をして振る舞いを証明しようとしてる.

実装とテストはほぼ終わって試験運用に向かう段階なのに順序が逆だろと思うけど, やってみないと気がつかない部分も多い.

気にしないといけない登場人物が一人か複数人かによって全くややこしさというのが違うのだなあと改めて思う. 普段は何か作り始めるときは極力単純なモデルになるように意識的, 無意識的にやっている. 受託開発だとそれを制御できないと死活問題だしね.

ところが今回は途中参加で既にある程度物があって動かせないから実装からモデルを抽出しないといけない. まるっきり普段と逆の考え方をする. 頭の体操になるし, うまく定義できると楽しいものだが, それらが終わって早く実際に使ってもらえるところにもっていきたいものだ.


御意見は光成滋生<herumi@nifty.com>までお願いします