kubo39's blog

ただの雑記です。

ベイルート・ロンドン・リバプール旅行記

3/25~4/2でベイルート(レバノン)、ロンドン・リバプール(イギリス)旅行に行っていたので雑に記録を残しておく

ベイルート

  • 直行便がないのでフランクフルト経由。フランクフルトのラウンジで食べたパン・ソーセージとビールがこの旅で一番うまかった可能性がある。
  • 到着後もろもろの事情で国防軍の出迎え&謎の力ですんなり滞在ビザを取得できてしまった。
    • 滞在ビザは長蛇の列で質問も結構長くやってたぽいので助かった。
  • ホテルはめっちゃいいホテル
    • やたらおいしいチョコレートが置いてあった
    • ベイルート市内からちょっと離れてるけど景色も綺麗だった
  • 近辺を散策していると生々しい銃痕の痕が。現在でも緊迫した状況を感じる。
  • ホテルで朝食をとっているとどんどんアラブ料理を勝手に運んできてくれてとてもよかった。ヨーグルトやフルーツジュースがめちゃ美味しい。
  • 市内散策ツアー
    • 前後を長銃もった兵士に囲まれるバスツアー
    • ベイルート国立博物館にいったけどローマ時代の遺跡はみられず延々鉱石をみることになった
    • アラブ料理のレストラン。なかなか美味しかったけどホテルのほうがおいしかった。ここでもチョコレートケーキ、そして美味しい。
  • 出国はけっこうたいへんで、3回は危険物チェックゲートを通ったとおもう。

ロンドン

  • ヒースローでかすぎ
  • むこうだとビートルズピンク・フロイドは別格らしい
  • ロンドンはそこかしこで中国語や日本語がきける
  • ホテルはそこそこ
  • イカー・ストリートでホームズ博物館へ。とくになにかあるわけでもないが雰囲気が味わえるのでけっこうよかった。
  • 本場のパブでフィッシュ&チップスとビール、まあこんなもんか
  • コベント・ガーデンでめちゃくちゃ歌うまいおっさんが歌っててよかった
  • 夕飯はチャイナタウンで中華
    • 普通の中華料理だけどイギリスじゃ別格にうまいってわけw
  • 本場ピカデリー・サーカスレ・ミゼラブルをみた
    • ジャベール役の人がめちゃくちゃよかった、多くの人が最後の登場のところでスタンディング・オベーションしていた
    • ソロで特によかったのは star, on my own, bring him home かな

リバプール

  • ホテルは建物すごかったけど設備とかつらかった
    • 朝食がびっくりするほどまずかった。あいつらこれうまいとおもってるんすか・・
    • リバプールはどこで食ってもイマイチだったけど
  • ビートルズゆかりの地を訪ねるツアーに参加
  • 中心部はけっこうオシャレタウン
    • 変なおっさんが火の輪くぐりのパフォーマンスしてた、日本じゃできないだろうな
  • 郊外は労働者の街といった感じ
    • 治安もあまりよくなさげ
  • アンフィールドでliverpool vs spurs観戦
    • 住宅地に突然あらわれるスタジアム
    • 肖像画ダルグリッシュ、ファウラー、ジェラード!など
    • あまりうまくないバンドが「wish you were here」や「Don't look back in anger」を歌ってた。ビートルズは逆に歌いにくい?
    • 生You'll never walk aloneに白熱した試合展開に泣きそうになる

けものフレンズ2を総括する

人にはそれぞれの感想がある、これは自分のために整理するくらいの意味合い

よかったところ

  • ゴマちゃんことゴマすりクソバードがいいキャラだった
  • フレンズのキャラデザがよかった
  • ごまちゃんやチーターの声優演技がよかった
  • 次回予告
    • かわいい
  • ペパプライブの映像クオリティ
    • ここは一期よりもよかったと思えるところ
  • キュルル ロシアのスパイ説

だめだめだったところ

  • 尾崎のサーバル演技がほんとうにひどい
    • 一期の演技が好きだったのでこれはわりと苦痛だった
    • かなりいらいらするレベル
  • サーバルbot感、自我を持っていないような感じ
    • すっごーいbotになっていた
    • 一期サーバルはかばんちゃんのために怒ったりツッコミ役やったり、しっかりキャラクターがあったのに
  • 違和感しか感じないかばんさん
    • サーバルちゃんやラッキーさんをいきなり呼び捨てにしているところ
    • それ自体が悪いというより背景がまったくないので違和感しかない
  • 全体的に脚本がひどすぎる
    • 話として単純におもしろくない
    • 前後のつながりに意味がなさすぎる
  • 設定のガバ
  • キュルルの叡智パート全体的にいらなかったし意味わからなかった
    • 7話でリレーにするのまじで意味がわからなかった
  • フレンズ型セルリアン弱すぎて話盛り上がる要素がない
    • 「緊迫感がまったくない」って言われてたけどほんとうにそのとおりとしかいいようがない
  • まじでいらなかったフウチョウコンビ、というかあれなんだったの?
    • メインの話にもぜんぜん絡まないし単体としてもなんも影響がない
    • 歌も演技も微妙なのでゴリ押しだったんだろうな

おきもち

  • だめなところがとことんだめだった
  • もっとひどいアニメ(例をあげるならいくらでもあるが魔法戦争とか)があるがそれがこの作品を許す理由にはならない
  • 制作陣のSNS対応もけっこうひどいなとおもった
    • アンチにあれだけ絡まれると精神的にもつらいだろうけどそれ抜きでも邪悪だよな
  • けフ2好きな人がいてもいいとおもうしそこを叩くのはよくない
    • ただけフ2好きな人はアニメ作品としての出来ではなくてけものフレンズプロジェクト自体が好きなんだろうなとはおもう
    • で、この人たちは現行の「けものフレンズプロジェクト」のやり方を(消極的かもしれないにしろ)認めているわけで、やっぱりわかりあえないんだろうな、と
  • キュルル叩かれすぎでは
    • 人間の子供としてはあんなものじゃないかなあ、そんなに悪人的な描写なかったとおもうんだけど

argc = 0 でプロセスを動かす

DMDのコード内ではargcが0な環境を想定してコードが書かれているが、そういう環境は存在するのだろうか。

調べてみるとわりとすぐにstackoverflowがみつかった。

c - executing a process with argc=0 - Stack Overflow

posix_spawnを使えばできるようなので、実際に試してみる。

#include <spawn.h>
#include <stdlib.h>

int main(int argc, char** argv, char** envp)
{
    pid_t pid;
    char* zero_argv[] = {NULL};
    posix_spawn(&pid, "/home/kubo39/dlang/dmd-2.085.0/linux/bin64/dmd", NULL, NULL, zero_argv, envp);
    int status;
    waitpid(&pid, &status, NULL);
    return 0;
}

たしかにargcが0のときの処理が実行されているようだ。

$ ./a.out
Error: missing or null command line arguments                                                                                                                               
$ echo $?
0

ちなみにrustcの場合は Error: couldn't determine self executable name と表示され、goでは panic: runtime error: index out of range になった。

Isabelleちょっとやってます、という近況報告?

ここ最近Isabelleを触っている。 Coqと比べると日本語の資料が少ないのでつらい。

とりあえずいろいろわからないことが出てきたので並べてみる。

2以上の自然数を扱うと簡約できない

例としてmap関数を定義する。

fun map :: "('a \<Rightarrow> 'b) \<Rightarrow> 'a list \<Rightarrow> 'b list" where
  "map _ [] = []"
| "map f (x # xs) = (f x) # (map f xs)"

value "map (plus 3) (1 # 2 # [])"

そのまま自然数の3や5を命題に含めると解くことができない。

lemma test_map1: "map (\<lambda> x. plus 3 x) ((Suc 1) # 0 # (Suc 1) # []) = (5 # 3 # 5 # [])"
  sorry

(Suc (Suc 1)) みたいにすると解ける。

lemma test_map2: "map oddb (Suc 1 # 1 # Suc 1 # (Suc (Suc (Suc (Suc 1)))) # []) = (False # True # False # True # [])"
  apply (simp)
  done

もちろんこれを続けるのは限界があるので、なんか方法を見つけないといけない。

命題にforallの有無がどう影響を与えるかわかっていない

別の命題を解いてしまっているのではないかという懸念がある。 goalだけみると同じにみえるけどよくわかってない。

forallがないとうまくいかない場合

以下は Failed to apply proof method と怒られる。

lemma mult_eq_0: "n * m = 0 \<longrightarrow> n = 0 \<or> m = 0"
  apply (simp)
  done

\<forall> n m::nat. を命題に足すと No subgoals までできる。

lemma mult_eq_0: "\<forall> n m::nat. n * m = 0 \<longrightarrow> n = 0 \<or> m = 0"
  apply (simp)
  done

forallがあるとうまくいかない場合

以下は Cannot determine type b. と怒られる。

theorem not_true_is_false: "\<forall> b::bool. b \<noteq> True \<longrightarrow> b = False"
  apply (case_tac b)
   apply (simp_all)
  done

\<forall> b::bool. を命題から消すと No subgoals! までできる。

theorem not_true_is_false: "b \<noteq> True \<longrightarrow> b = False"
  apply (case_tac b)
   apply (simp_all)
  done

小ネタ: safe functionとcast function of null

おなじみ(?), safe functionを考えてみようの会です.

突然ですが, 以下のコードは合法でしょうか?

void main() @safe
{
    (cast(void function() @safe) null)();
}

結果はおおかた予想がつくとは思いますが、Segmentation Faultになります。

(dmd-2.084.0)$ rdmd safecastnull.d
zsh: segmentation fault (core dumped)  rdmd safecastnull.d

さて、合法かどうかですが, まず safe functionの定義 から照らし合わせてみると

  • No casting from a pointer type to any type other than void*.
  • No casting from any non-pointer type to a pointer type.
  • No pointer arithmetic (including pointer indexing).
  • Cannot access unions that have pointers or references overlapping with other types.
  • Calling any system functions.
  • No catching of exceptions that are not derived from class Exception.
  • No inline assembler.
  • No explicit casting of mutable objects to immutable.
  • No explicit casting of immutable objects to mutable.
  • No explicit casting of thread local objects to shared.
  • No explicit casting of shared objects to thread local.
  • No taking the address of a local variable or function parameter.
  • Cannot access __gshared variables.
  • Cannot use void initializers for pointers.
  • Cannot use void initializers for class or interface references.

一個目と二個目が怪しいですね.

nullの定義 もみてみましょう. 最初の文はnullはポインタやら動的配列やらを表すことができる値と解釈できます.

次ですが, なんと, "it is an exact conversion to convert it to the null value for pointers, pointers to functions, delegates, etc." という文言が確認できます. 正しい変換であるとはどういうことなんなんでしょうか. 必ずsafe? また直前のnullは別の型にキャストされる前だとtypeof(null)という型が与えられるそうですが, これはpointer typeなのでしょうか, それともnon-pointer typeなのでしょうか. 少なくとも現在の仕様の状態から判別することは難しいと思います…

というわけで最初の問題の結論は, 「現時点(2019.02.07)での仕様ではどちらともとれる」でした(これはひどい)

scope parameter storage class と in parameter storage class

(2019/02/07; いくつか追記したのでそちらのほうもみてください)

現在の最新版コンパイラ(DMD 2.084.0)だと, このコードはSEGVで落ちてしまう.

auto foo(scope void delegate() @safe dg) @safe
{
    return dg;
}

auto bar(void delegate() @safe dg) @safe
{
    return foo(() => dg());
}

void main()
{
    bar((){})();
}

これは関数 bar fooがクロージャを生成しないためである. (scope parameter storage classを使わない場合は callq _d_allocmemory がコード生成される, これはクロージャのためのメモリを確保するために使われる)

(追記: たしかにscope使った場合にclosureを生成しないためなんだけど、実際には開放済のはずのスタック領域に対して不正アクセスしてるのが問題なのであり、しかもbarではなくfooのほうだった…)

0000000000032edc <_D9scopesegv3fooFNfMDFNfZvZQh>:
   32edc:       55                      push   %rbp
   32edd:       48 8b ec                mov    %rsp,%rbp
   32ee0:       48 83 ec 10             sub    $0x10,%rsp
   32ee4:       48 89 7d f0             mov    %rdi,-0x10(%rbp)
   32ee8:       48 89 75 f8             mov    %rsi,-0x8(%rbp)
   32eec:       48 8b 55 f8             mov    -0x8(%rbp),%rdx
   32ef0:       48 8b 45 f0             mov    -0x10(%rbp),%rax
   32ef4:       c9                      leaveq 
   32ef5:       c3                      retq

正直バグなのかどうか確定的に言えないが, 関数の定義において scope parameter sotrage class は ~ cannot be escaped といっているのでこれは合法的な振る舞いだと思われる. (追記: 触っちゃいけないスタック領域さわってるからアウトだな…)

この挙動がバグであるかはおいておいて, scope parameter storage classと似たようなものとして in parameter storage classがある. ただしここで scope のかわりに in を使った場合, クロージャを生成するコードがはかれてプログラムは正常終了する.

https://dlang.org/spec/function.html#param-storage によると in parameter storage class は defined as scope const. However in has not yet been properly implemented so it's current implementation is equivelent to const. ~ とある. この説明を読むと in は scope const と定義されているけど, 現状では正しく実装されていなくて const と同じ扱いだよ, なるべく使わないでかわりに scope constconst を使ってね ということだそうだ.

というわけだが, いくつか疑問なところがでてくる.

  • inの現在の実装は正しくなくてconstと同じ実装だよ, と仕様に書かれている場合に段階的にscope constと同じようにコンパイラ内部の実装を変更するべきだろうか
    • constともscope constとも異なる実装になる? stableでおきなかったらいいか
    • ユーザは仕様の注釈をみて const と同じ振る舞いを期待してコードを書いているかもしれない, さすがに無視してよさそうだが...
  • さらに上の問題のように, in parameter storage classの実装をscope constに近づけたことによっておきる問題が発生した場合, どのように対応すべきだろうか
    • 仮に今回のscope parameter storage classの挙動(SEGV)が正しい場合があるとして, ユーザがコードを変更してないのにコンパイラのバージョンをあげたらSEGVる, とか.
    • そもそもコンパイラのBug Fixなので正しいし, ユーザが in parameter storage class の使い方を間違えているケースと考えてることはできる.
    • とはいえわりと使われていそうなので, かなり影響がでそうだし慎重にやるべきだろうな.

とりあえず現時点で in paramter storage class は使わないほうがよさそうだ.

DMDでprofileオプションを使ったときだけスタックオーバーフローする不思議なバグの調査

なんかおもしろいコンパイラのバグに遭遇したのでめも。

もともとはpeggedを使ったプロジェクトで dub build --build=profile でビルドしたバイナリを実行するとSEGVに遭遇するんだけど・・・、という問題だった。

とりあえずgdbにかけてみると

>>> bt 10
#0  0x0000555555928ecc in trace_pro ()
#1  0x000055555580cbb8 in _D4core8internal6string__T7dstrcmpZQjFNaNbNiNeMxAaMxQeZi ()
#2  0x0000555555955b62 in trace_addsym ()
#3  0x0000555555928fee in trace_pro ()
#4  0x000055555580cbb8 in _D4core8internal6string__T7dstrcmpZQjFNaNbNiNeMxAaMxQeZi ()
#5  0x0000555555955b62 in trace_addsym ()
#6  0x0000555555928fee in trace_pro ()
#7  0x000055555580cbb8 in _D4core8internal6string__T7dstrcmpZQjFNaNbNiNeMxAaMxQeZi ()
#8  0x0000555555955b62 in trace_addsym ()
#9  0x0000555555928fee in trace_pro ()
(More stack frames follow...)

という感じでどうも相互関数呼び出しによるstack overflowをしている。 そのあとごにょごにょ回り道をしたあげく、DWARFの吐いている core.internal.string.dstrcmp のソースでは存在していないけれど、disassemble では callq trace_pro するようになっていてなんだこれ?ということでDMDのソースをみることに。 すると src/dmd/glue.d に以下のようなコードとコメントが。

        /* Doing this in semantic3() caused all kinds of problems:
         * 1. couldn't reliably get the final mangling of the function name due to fwd refs
         * 2. impact on function inlining
         * 3. what to do when writing out .di files, or other pretty printing
         */
        if (global.params.trace && !fd.isCMain() && !fd.naked)
        {
            /* The profiler requires TLS, and TLS may not be set up yet when C main()
             * gets control (i.e. OSX), leading to a crash.
             */
            /* Wrap the entire function body in:
             *   trace_pro("funcname");
             *   try
             *     body;
             *   finally
             *     _c_trace_epi();
             */
            StringExp se = StringExp.create(Loc.initial, s.Sident.ptr);
            se.type = Type.tstring;
            se.type = se.type.typeSemantic(Loc.initial, null);
            Expressions *exps = new Expressions();
            exps.push(se);
            FuncDeclaration fdpro = FuncDeclaration.genCfunc(null, Type.tvoid, "trace_pro");
            Expression ec = VarExp.create(Loc.initial, fdpro);
            Expression e = CallExp.create(Loc.initial, ec, exps);
            e.type = Type.tvoid;
            Statement sp = ExpStatement.create(fd.loc, e);

つまり dstrcmp 関数がtrace_pro を先頭で呼ぶように書き換えられた結果、 trace_pro -> trace_addsym -> dstrcmp -> trace_pro -> ... のようにひたすら関数呼び出しをし続けてstack overflowになる、ということだ。

peggedを使わない場合の再現コードをなかなか用意できなかったが文字列の大小比較 ("aaa" >= "bbb") は内部で dstrcmp を呼ぶので非常に簡単な再現コードに落とし込むことができた。 つまり文字列の大小比較のあるすべてのコードは -profile オプションを用いると同様にstack oveflowになる、ということである。だれも-profileオプションを使っていないのだろうか。

今回の話の教訓としては、「コードは書いたとおりに動くというのは嘘、コンパイラは隙があればおまえのコードを勝手に書き換える」ということですね。