近況 2018-11-30

1ヶ月分のコミットメッセージとツイートを眺めていろいろ書くやつ、3回目

hsp3-debug-ginger

https://github.com/vain0x/hsp3-ginger/tree/main/hsp3-debug-ginger

HDG: knowbug について

以前に HSP3 用のデバッガー knowbug を作った

  • knowbug
  • ほとんど完成しているので、更新していない
  • いくつか心残りがある:
    • 配列とかを1本の文字列 (YAMLっぽい形式) で表していて不便だった
    • 配列の要素やメンバ変数をツリービューのノードにしようとしてたけど、デバッグの途中で放棄してた
    • HSP3 の utf-8 版に対応したい
    • 未対応の改善要望が来ている
  • 生の Win32 API で GUI やってるところがきつくて knowbug はあんまりいじりたくない

VSCode 用のデバッガーとして新たに作っているのが hsp3-debug-ginger

HDG: Debug Adapter Protocol (DAP)

hsp3-debug-ginger は Debug Adapter Protocol という、開発ツール (IDE) とデバッガーと接続するプロトコルを使っている

  • 例えばエディターのデバッグ UI で停止ボタンを押したら、プロトコルに則った形式でデバッガーに停止イベントが通知されて、プログラムが停止する、みたいな感じ
  • 開発ツールからすると、何をデバッグしているか知らなくていい
  • デバッガーからすると、ユーザーがどの IDE を使っているか知らなくていい
  • VSCode はデバッガーと標準入出力で接続するけど、 HSP のデバッガーは DLL なので標準入出力を使えない

HDG: hsp3-debug-ginger の構成

  • VSCode と標準入出力でつながる exe と、HSP からロードされる DLL を別個に作って、 WebSocket で繋げる、という構成にした
  • WebSocket をわざわざ使う意味がないので TCP に置き換えた
  • Windows のプロセス間通信はどうやるのか知らない
  • 両方 Rust で書いてる
  • C++ だと WebSocket とか TCP とかマルチスレッドとか大変そう
  • マルチスレッドの扱いはアクターモデルみたいな感じになってきた
  • mpsc チャネルという、複数のスレッドから単一のスレッドにメッセージを送信する仕組みを使っている
  • Rx.NET でいうと複数のスレッドに渡した Subject を Observable.Merge して単一スレッドで Subscribe する感じ
  • これでメッセージを単一のスレッドにディスパッチしてすべての処理をそこでやれば安全
  • と思ったけど HSP のメインスレッド上で行うべき処理が一部あって、どうするか困ってる

HDG: 課題

はやくテストを書きたい

  • どこを分離したらテストできるのか分からない

milone-lang

https://github.com/vain0x/milone-lang

ML: クロージャ変換

クロージャ変換が入った

これはいくつかのユースケースに分かれる

// before

// 1 から u まで fizz buzz を表示する
let fizzBuzz u =
    let rec go i =
        if i <= u then // この u は関数 go のローカル変数じゃない
            if i % 3 = 0 then
                // 
            go (i + 1)
    go 0

fizzBuzz 15
// after

let fizzBuzz u =
    let rec go (*追加*)u i =
        if i <= u then
            if i % 3 = 0 then
                // 
            go (*追加*)u (i + 1)
    go (*追加*)u 0

fizzBuzz 15
  • 2つ目は、 関数呼び出しの引数が足りないケース https://github.com/vain0x/milone-lang/commit/e16e9418b09d66399c8e19e33a3b758f0328e43c
  • 引数束縛して関数オブジェクトを作る必要がある
  • 関数オブジェクトはC言語の上では「関数」ではなくただの値なので f(..) という構文では呼べない
    • だから milone-lang の構文的には同一の式でも、関数オブジェクトを呼んでるのか関数を呼んでるのか区別する必要がある
  • 関数オブジェクトは内部に持っている状態を隠蔽する必要がある
    • C言語の上で型に現れると互換性がなくなるから
    • void* を使うしかない
    • 最適化の効きが悪くなるので、すべてこれにするというわけにはいかない
let add x y = x + y

// add は2引数なのに引数が1個
let inc = add 1

// 関数ではなく関数オブジェクトの呼び出しとしてC言語にする必要がある
inc 9
// コンパイル後のイメージ

int add(int x, int y) {
    return x + y;
}

// add を関数オブジェクト int -> int から呼べるシグネチャにしたもの
int add1(void* t, int y) {
    return add(*(int*)t, y);
}

// 関数 int -> int をコンパイルした結果
struct IntIntFun {
    int(*fun)(void*, int);
    void* env; // 関数オブジェクトが持っている状態
}

// 関数オブジェクトを作る
// 状態と関数のペア
int* x = (int*)malloc(sizeof int);
*x = 1;
struct IntIntFun inc = {
    .fun = add1,
    .env = (void*)x,
};

// 関数オブジェクトを呼び出す
// inc は関数ではないので inc() とはできない
inc.fun(inc.env, 9);
  • 関数の引数がないケース (let f = add みたいなやつ) もこれと同様に処理できる
    • 関数参照は引数ゼロの引数束縛
  • あと 関数の引数が多いケース (複数の呼び出しに分解する必要がある) とかある

ML: ユニオン型

ユニオン型 (判別共用体型) が入った https://github.com/vain0x/milone-lang/commit/2a994c3aa8bae0b4be38c2e5bbfee0a913e0e6d4

ただし再帰的定義はできない

ML: ステージ順序

いままでのソースコードの変換過程は、 パース → 型つけ → MIR化 → クロージャ変換 → C言語化、だった

  • MIR化: パターンマッチを条件分岐に変換しつつ、入れ子になった式を平坦にする (K正規化する) という手順

クロージャ変換は静的コード生成に近いものがある。これを MIR に対して行っていたせいで、入れ子になった式やパターンマッチを生成できなくて、生成処理が分かりにくかった。だから順番を入れ替えてる (作業中)

パース → 型つけ → クロージャ変換 → MIR化 → C言語化

ML: その他

  • y |> f x は構文的には f x という関数オブジェクトに x を適用してるけど、実際には f x y が呼ばれるのでゼロコスト、というのが分かった

競プロ参戦記

AtCoder でのコンテスト開催が少なかったため3回だけ

転倒数の記事の閲覧数がなぜか増え続けていることに気づいた。わりと尻切れトンボな記事だったので、 BIT について加筆した

円周率チャレンジ

2 を足す操作と平方根をとる操作を繰り返して、数値を円周率に近づけるゲーム。半分全列挙を使って手順を探索した

ブログ

  • ヘッダーに競プロ参戦記等へのリンクを貼った
  • ローカライズのあやしい部分を修正した

Web アプリ開発

  • 前月同様、昨日は増えてるが品質は下がってる
  • リファクタリングがいるのは分かるけど、どういう方向に持っていけばいいのか……

ガルパ

  • 難度 26 楽曲でもフルコンボがたびたび出るようになってきた
  • 難度 27 楽曲を回復なしでクリアできるように練習してる
  • 左手プレイは練習してもうまくならないという限界を感じる
    • 片方をホールドして動かしながら他方でタップ、みたいなのができない

関連記事