MathJaxで私が書くぐらいの内容をSATySFi化するツール

概要

desmos.comなどのサイトから数式をコピペしてくるとそのままMathJaxで使えて便利なのだが、それをSATySFi化しようとすると括弧とかをいちいち\paren{}とかに手動で置き換えていくのがあまりにも面倒である。これはかなり単純作業なので、ツールが欲しくなった。もちろん意味論を見て\paren{}\app{}{}にするとかは手動じゃないとできないが。

とりあえずSATySFi化するツールなのでSATySFifyとかいう名前にしてみる。ググってみても衝突しないっぽいのでOK。

github.com

一番面倒なのが数式の変換なので、まずはそれを作るところから始めることにした。

開発期間

2019年9月末~10月頭に9日間*1ぐらいやって、その後放置してしまっている。多分SATySFiを今後使っていくにつれて私ももっと機能が欲しくなってくるはずで、そうなれば機能がもうちょい増えるはず。きっと。

あっプルリク大歓迎です。今さっきMITライセンス足しました

現状での機能

まあMakefileに直書きしてあるテストを見るのがわかりやすいのだが、

  • ()\paren{}に変換
  • \left( \right)だったり\left. \right|_{a=x, b=p}だったりにもちゃんと対応
  • 行列もちゃんとmatrix, bmatrix, pmatrix, vmatrix, Vmatrix に対応。行列の実装は、 nekketsuuuさんのsatysfi-matrixRust側に埋め込むという手抜き実装である
  • あとは\hbar*2とかに追加で対応している。そのため、Rust側のソースコードに登録しておくことで、ライブラリにないやつを出力ソース中に埋め込める仕組みを実現してある

あれっそんなに機能ないな*3……まあMathJaxで私が書くぐらいの簡単な内容でSATySFiと互換のない部分ってのは主にここぐらいだからか。

今後足したい機能

  • \dot とか \vec が欲しい
  • 数式単体じゃなくて、数式の入ったMathJax文書をそのまま変換できるようにしたい。http://draft.hyuki.netで書いた下書きをそのままツールに投げ込んでSATySFiとして使えれば理想的
  • MathJax側は無駄な{}を許容するが、SATySFiは許容しない。これを処理するのは結構たいへんで、例えば現状の実装だと「\fracは引数を2個取る」とかは一切見ていないので厄介
  • alignされたやつの処理。行列では対応できているので、それをちょろっといじればできると信じたい
  • 不正な入力を落とす方のテストも書かねば
  • ブラウザで動くREPLほしい
  • \dots問題。真面目に対応するのが面倒だったので、現状では`\dots` detected; converting it into `\ldots` (you might want to fix this)というwarningを出すという運用にしている

*1:2019/9/28, 9/29, 9/30, 10/1, 10/2, 10/4, 10/5, 10/10, 10/13

*2:私は物理学科にいるので、これがないと困る

*3:でもこの比較的少数の機能を実現するために意外とコードを要したのよな。もっと構文解析ライブラリを活かすと削れるのかもしれないが

SATySFi<s>ライトユーザー</s>分かってないマンの所感

おことわり

筆者はThe SATySFi bookをまだ読んでいないライトユーザー、いわゆる分かってないマンです。この記事書き終わったタイミングで読んで買います。この記事を読んであなたのSATySFi力が上がることはおそらくありません。

そうそう

この記事はSATySFi Advent Calendar 2018の23日目の記事です。

内容

「The SATySFi bookをまだ読んでいない素人がSATySFiを使っている様子」を(主にツイートから)振り返ってまとめた記事。(Togetterでよかったのでは?)

何に使っているか

  • 数学絡みの授業で毎週出る課題
    • 確率統計の授業用(学期前半はTeXを使っていたが乗り換えた): 44KiB
    • 群論の授業用: 105KiB
    • …あれ?これらの授業にしか使ってなくない?
    • もっと使っていきたい
    • 公開したいが、諸事情により公開してよい保証がない。残念。

使っていてハマった点

分数累乗バグ

SATySFiの発展に貢献できた。わーい。

改ページ周り

文章を書いていると稀にこれに抵触し、そのたびに空の+pn{}を挿入する必要が発生している。逆にいえば空の+pn{}を挿入するだけで解決してしまうため、そんなに困っていない。

\int^{c}_{\infty}とは書けない書けなかった

version 0.0.3で試してみたらちゃんと両方動いた。ということはこれバグだったのか。

長い数式がページから溢れたときの対策方法

間違いなくThe SATySFi bookに載っているはずなので調べる。

絶対値とかってどう書くの

これもまたThe SATySFi bookに載っているはずなので調べる。

対症療法

local.satyhに

    let-inline ctx \hide inner =
    let ctx-hide =
      ctx |> set-text-color (CMYK(0., 0., 0., 0.))
    in
      read-inline ctx-hide inner

とかいうものが生まれることになった。

フォントの差

これはスクショを貼らないと分かりづらいので補足する。私のパソコン(Mac)上で件のpdfを表示したときには次のようになる。

f:id:hsjoihs:20181223210514p:plain

一方、GitHubにpushしてからpdfのGitHubでの表示をSafariで見ると

f:id:hsjoihs:20181223210730p:plain

となって、右にズレる代わりに表示はされる。

わからん。つらい。

最初期に入れたのでアプデが面倒だった

特にhashでハマった。

感想としては、

というのがある(バックアップを取っていなかったため、途中から「これ途中で諦めるとSATySFiそのものが使えなくなるので、アプデを成功させるしかない」という状況になり、つらかった)。

satisfiedの綴りを間違えそうになる

 「satysfi【動詞】(三単現: satysfies, 現在分詞: satysfiing, 過去・過去分詞: satysfied):組版ソフトSATySFiを用いて組版すること。」が辞書に載るようになるぐらいSATySFiを盛り上げていくことで解決される問題である。*1

使っていて思ったこと

フォーマッターほしい

フォーマッターほしい。作りたい。

Haskellやっててよかった

まだThe SATySFi bookが紙でしか公開されていなかったタイミング(しかも当時日本にいなかったため紙のにもアクセスできず)で、表の罫線をいじる必要が発生したことがあった。

具体的には、群論の授業で「九九表*2を書け」という課題が出た。

Haskellをやっていたおかげで、demo.pdfしかヒントのない状況で以下のようなコードを書くことに成功した。

\tabular(fun cellf multif empty -> [
  [cellf {Table}  ; multif 1 1 {${0}};  multif 1 1 {${3}}; multif 1 1 {${6}}; multif 1 1 {${9}};];
  [cellf {${0}}; cellf {${0}}; cellf {${3}}; cellf {${6}}; cellf {${9}};];
  [cellf {${3}}; cellf {${3}}; cellf {${6}}; cellf {${9}}; cellf {${0}};];
  [cellf {${6}}; cellf {${6}}; cellf {${9}}; cellf {${0}}; cellf {${3}};];
  [cellf {${9}}; cellf {${9}}; cellf {${0}}; cellf {${3}}; cellf {${6}};];
])(fun xs ys -> (
  match (ys, List.reverse ys) with
  | (y0 :: y2 :: _, ylast :: _) ->
    ( match (xs, List.reverse xs) with
      | (x0 :: x1 :: x3 :: _, xlast :: _) ->
        let grlstY2 =
          [y0; ylast] |> List.map (fun y ->
            stroke 1pt Color.black (Gr.line (x0, y) (xlast, y)))
        in
        let grlstX2 =
          [x0; xlast] |> List.map (fun x ->
            stroke 1pt Color.black (Gr.line (x, y0) (x, ylast)))
        in
           (stroke 0.5pt Color.black (Gr.line (x1, y0) (x1, ylast)) ::
              stroke 0.5pt Color.black (Gr.line (x0, y2) (xlast, y2)) :: 
                List.append grlstX2 grlstY2
           )
      | _ -> []
    )
  | _ -> []
));

当時は日本国外でThe SATySFi bookを入手する手段が乏しかったため読んでいなかったのは仕方がないが、この記事を書いている段階ではオンラインでも閲覧ができ、そもそも私が日本国内にいる。そんな状況で読んでいないというのはただの怠慢なので読んでいきたい。本当はネット公開された時点で読もうと思ったのだけれどpixivアカウントを作るのが面倒で放置していた

ブロックコメントってあるんだろうか

あるのか分からないので全部の行頭に%をつけている。「コメントモード」とやらは%のみで発生する、みたいな記述を見たことがある気がするし、これが正解なのでは。

ちなみに私は「SATySFiに複数行コメントがほしい」と主張しているわけではなく、

などを考えると「複数行コメントなどない」というのも普通によい設計だと考える。(ここまで書いておいて「ありますよ〜」って言われたらどうしよう)

エラーメッセージのわかりやすさって正義ですよね

The SATySFi book、読まなきゃなんだよなぁ

The SATySFi bookを読まないとライブラリが足りないときにライブラリを書けないので、The SATySFi bookを読まねばならない。それはそう。

追記

記事を公開した後、

 ということになったので、「ライトユーザー」を「分かってないマン」に変更。

*1:冷静に考えると、これでは「『満たされた』という義の語を書きたいときに綴りを間違える」という問題は普通に解決されていない。

*2:分かっている人のために注記しておくと、Cayley table

C(のサブセット)コンパイラを書く上でハマった点:配列編

初めに

「配列編」と銘打っていますが、続編が投稿される保証はありません。

そうそう

この記事は言語実装 Advent Calendar 2018C言語 Advent Calendar 2018の17日目の記事です。

想定している読者層

(どういう読者層を想定しているんだろう、書いていて自分でもよく分からなくなった)(Cコンパイラ書いていて「配列の配列(いわゆる二次元配列)がなんかバグるなぁ」となった人のための記事かもなぁ)(というか、多分バグらせていた当時の自分への手紙)

本題に入ろう

Cコンパイラを書く上で微妙にハマった、配列へのポインタの話、それに付随して構造体を実装する際の話について軽く書きます。

まずは結論から

 正直上2ツイートで私が今回言おうとしている話はだいたい尽くされているんですが、一応書いていきます。

ポインタ+整数

C言語では、「ポインタが難しい」と良く言われますが、 実際に初心者がCを学習する過程を見ると、以下のことだけは すぐに理解しているようです。
「ポインタっつーのは、要するにアドレスのことなんだな」
ここまでは簡単、誰でもすぐに理解します。

http://kmaebashi.com/programmer/pointer.html

ポインタに1足したら、2byteとか4byteとか進む、ということを習った時から ? が点灯する。「ポインタってアドレスなんだろ? そんなもん1進むに決まってるんじゃないのか?」

http://kmaebashi.com/programmer/pointer.html

一般にT *型のポインタpに1を足すと、アドレスはsizeof(T)だけ増える*1ので、C言語においてポインタを扱う際には、アドレスだけではなくポインタの型が何であるかという情報も不可欠です。まあそもそも型が分かっていないと間接参照((ポインタに*を適用すること))もできませんが。

 

ちなみに、話が逸れますが、アドレスが同一で型が同一でもポインタとして同一であるとは限りません。

これを知らないと、

みたいなことになったりするようです。私も上のやりとり見て初めてこの話を知りました(そして前述のqnighyさんのツイートで腑に落ちた)

配列とポインタ、&*

C言語には、「配列」と「配列の先頭要素へのポインタ」と「配列全体へのポインタ」があります。例えば、int arr[8]; int *p1 = arr;とあるとき、arrは配列であり、p1は「配列の先頭要素へのポインタ」であり、&arrは「配列全体へのポインタ」です。&p1は「ポインタへのポインタ」ですね。int *p1 = arr;と書けるのは、式の中では基本的に「配列」が「配列の先頭要素へのポインタ」へと勝手に読み替えられるからですね。

Cコンパイラを書いた時点で私がちゃんと分かっていなかったのは、「ポインタ&arrのアドレス値『は』ポインタp1のアドレス値と等しく、これを間接参照する際には

アドレスが指す先をメモリから読むって処理を飛ばさないといけない

https://twitter.com/0x19f/status/1028028845805326336

 」という話です。

 

やっぱりみんなこれ疑問に思いますよね

今学期(2018年秋学期)取っていた授業で、配列arrがメモリ上のどこにあるのかを調べる際、講師がgdbp &arrとしていました。その結果、授業のQ&Aフォーラムで次のような質問がなされることとなりました:

文字列sに対して、gdbp sしたときとp &sしたときでアドレスが違うんですが、これはなぜでしょう?

これに対して私が返した解説をそのまま日本語訳すればいい説が出てきたので、ほぼそのまま和訳して載せていこうと思います。

 

「講義だとp &arrで文字列のアドレスが得られたのになぜ今回はそうならないのか、ということですよね?これは配列とポインタが別物であることに由来します。Cでは、 sizeof&オペランドになっている場合を除き、配列はその先頭要素へのポインタに暗黙に変換されます。scharへのポインタである場合、sというのはとあるアドレスを表すビットパターンであり、そのビットパターンはまたメモリのどこかに格納されています。前者がsで後者が&sなので、値が違うわけです。」

scharの配列である場合は話が少し変わってきます。sが配列で、アドレス0x7fffffffe900から始まるとしましょう。sは配列なので、先頭要素へのポインタへと変換されます。先頭要素は0x7fffffffe900という場所にあるので、sの値は0x7fffffffe900です。」

「では、scharの配列であるとき、&sはどうなるでしょう?実はこれは配列全体へのポインタという意味になります。講義のビデオをよく見ると、gdb&sの型がchar (*)[6]と表示されていることが分かります。これは、&sが(ポインタへのポインタではなく)配列へのポインタであるという意味です。さて、ということで&sは配列が格納されているアドレスですが、これも0x7fffffffe900です。ゆえにs&sは同じアドレスとなる(けれども型は違う)のです。」

「では、そもそもなぜこの『配列全体へのポインタ』とかいうよく分からないものが存在するのでしょう?これは主に多次元配列というものを許容するためにあります。*2。」

「Cでは、intの配列の配列を作ることができます。具体例として、3つの『5個のintからなる配列』からなる配列を考えましょう。これはint arr[3][5];として宣言できます。arrなんかの配列なので、大体の文脈でなんかへのポインタに変換されます。変換されてできるものは何でしょう?『5個のintからなる配列』へのポインタです。(Cの構文は奇怪なので、これをint (*p)[5]と書きます。)」

 「3つの『5個のintからなる配列』からなる配列int arr[3][5];では、intを格納する15個の『箱』全てが隣り合って存在することが保証されています。この条件の元で多次元配列を正しくサポートするという要請こそが、sが真の配列であるときにs&sのアドレス値が一致しなければならない理由です。int arr[3][5];に対して、arr[2][4]、つまり*(*(arr+2)+4)が最後の箱を表すようになってほしいわけです。」

(原文ではここに*(*(arr+2)+4)を追いかける文章が入るが、K&Rとか「C言語ポインタ完全制覇」とかに載っている図の方が分かりやすいので割愛)

「ということで、コンパイラが『配列全体へのポインタ』を間接参照するときには、実は型が変わっているだけであり、間接参照に対応するアセンブリが吐かれたりしません。このことによって、多次元配列が動いてくれるので、一貫性のため、全ての『配列全体へのポインタ』はこの性質を満たさないといけないのです。」

 

 補足:構造体

 注: (char *)&s + offsetof(struct A, arr)を間接参照する前に当然*(s.a)の型にキャストして戻さないといけないが、上ツイートではその話が抜けている

 最後に

今日あと4分しかないのでとても雑

*1:か、undefined behaviorを踏む

*2:「〜するためにある」といえる直接的な根拠を私は知りませんが、配列に&が付けられるようになったのがANSI Cになってからとかだった気がすることを考えると、「先に多次元配列があって、それと辻褄を合わせるために残りを整えた」と考えるのが自然ではないかなぁと思ったりします。根拠はありません。

hsjoihs_c_compilerの現状

この記事はセキュリティキャンプ 修了生進捗 #seccamp OB/OG Advent Calendar 2018の15日目の記事です。

概要

セキュリティ・キャンプ2018で作ってきていた自作Cコンパイラが9月4日にセルフホストを達成した後、どうなっているのかを軽く述べる。

セルフホスト達成時からあった機能

C89の演算子のうちキャスト・ sizeof 値 以外全部

キャストはvoid *などを用いることによって代用でき、sizeof 値sizeof(型)で代用できるため。あとポインタからポインタを引く場合、sizeof (*ポインタ)が1, 2, 4, 8以外だとダメ。

goto以外の制御構文全て

正直gotoぐらいすぐ足せると思うし足したい

型: void・char・int・ポインタ・配列・構造体・enumをサポート

shortとかlong longとかunsignedとかがない。charはunsigned charだけど。

文字列リテラル(連結・エスケープシーケンスなども)

流石にこれがないとね

関数から構造体を値として返す

ただし手抜き実装なので、「小さい構造体でアラインメントが4未満」だと動かない。

その後足した機能

文字リテラル

セルフホスト時はギリギリなかった。

構造体を値としてもっと使えるように

(構造体, スカラー)だったり(構造体 = 構造体).aだったりフラグ ? 構造体 : 構造体だったり。

Ubuntu対応

いつのまにかUbuntuで動かなくなっていた。gccだと-no-pieというオプションが必要になったりする、とかの対応をし、再び両方で動くように。

プリプロセッサ

とりあえず#include相対パスが正しく動かない)・#define(ただし置き換え後は単一のトークンであることが必要)・##ifdef#ifndef#endifを追加したことにより、2018年10月7日にプリプロセッサ込でセルフホスト。

関数ポインタ

これで(**********printf)("Hello, World!\n");ができる

構造体のメンバの配列の要素にアクセスすると落ちるバグの修正

「配列へのポインタ」、やはりおそろしい

ポインタからポインタを引いた時、任意のサイズで割れるように。

そもそもコード書く量を減らすための手抜きだったので。

前述の構造体アラインメント問題を修正

そもそもコード書く量を減らすための手抜きだったので。

行コメント

なんか足し忘れてた。

__func__

意外と便利。

可変長引数

作る方も呼ぶ方も対応。

中間表現のdump

--ir=でダンプを吐けるようにした。今後IRが複数発生したら名前どうしよう。現状では、「英語の語頭文字の確率分布に従って乱数で名前を決め、あとでバクロニムする」を考えている。

現状ない機能

とはいえ、いろいろ足りない機能もまだまだある。

typedef

typedefがない。va_listとかFILEとかで困る。

7個以上の引数を取る関数

引数をスタックに積み残す必要があるが、その位置が正しくない。

構造体を値で関数に渡す

スタックだったりレジスタだったりする。面倒。

union

structのオフセットを全て0にすればだいたい実現できるが、自分で使ったこともないしまだ実装していない

enumの初期化子

定数式周りをきちんとやっていない

int array[] = {0, 1, 2, 3};

つらそう

複数のトークンに展開される#define

再帰を禁止したり確保するメモリが増えたりとかがめんどい。Rust使いたい。

空文

これこそ一瞬で足せるのでは。

short, long long, unsigned

暗黙の型変換とかを考えるのがとても険しい。ちなみにmallocとかのsize_tsigned intになってしまっているが、x86-64において64bitレジスタの下32bitに書き込むと上位32bitはゼロクリアされてくれるため、レジスタで渡している限りではuint64_tへのキャストが勝手に発生してくれる。

真面目なconst

現状constというキーワードを読み飛ばしているだけ。

最適化

現状、x64をスタックマシンとして使っていて、そこにコンパイルする形になっている。

ということで最適化をしていくことになっているので、やらないとなぁ。

セキュリティ・キャンプ2018参加記

セキュリティ・キャンプ2018で作ってきていた自作Cコンパイラが、9月4日にセルフホストを達成したので参加記を書いていく。

(執筆はプリプロセッサを実装してからにしようとも思っていたが、アンケートの結果「先に記事を書くべし」とのことだったので)

 

応募とか

sksat_tty氏とかが言及していたことをきっかけに興味を持ち始め、↓に直接後押しされる感じで「じゃあ応募してみるかなぁ」と思い始めた。なお、この段階では「思った」だけであった。

 

なんかCコンパイラゼミとかいうのが行われるらしい。Cなら趣味で仕様とか読んでるし(書いたことはあんまりないけど)楽しめそう。 

 

月日は流れ5月後半。締切も近づきTLがセキュキャンで盛り上がり始める。なお私は書き始めてすらいない。 

 

とりあえず応募用紙を7296文字書いて出した。*1メールの履歴を見る限り、応募を提出したのは5月27日だそうだ。

 

そして締切がやってくる。

 

受かった

6月14日に合否発表があった。受かっていた。感謝の気持ちにあふれるなどした。

私は(当時バタバタしていたこともあり)合格したことをツイートしそこねており(他人の合否ツイはRTしていたというのに)、数日後に「あれ、セキュキャン来るんですよね?」というDMが講師のhikalium先生から来てしまったので慌ててツイートをした。

 不安感を与えてしまって申し訳ないなどと思った。

やっていき

6月下旬には事前学習slackが立ち上がり、

 7月8日から開発が始まった。なお、開発日記は↓に書いている。

github.com

セキュキャン前日

なんか天気が荒れており、雷が鳴っていたので念の為パソコンをコンセントから外しておいたりしていたところ、slackの方で「前泊だけどずぶ濡れになった」という情報が流れてきた。大変そう。

とか思ってのんきにコードを書いていたところ*2

 とかいう話がtwitterとslackに流れてきていた。つよい。ちなみに当時の私はポインタ・配列・char・文字列リテラルとかが実装されている程度の進捗であった。

 

あと、

 とかいうのが流れてきたので「これ、手元の本を色々持ってっといた方がよいのでは」という気持ちになるなどした。

セキュキャン当日 (Day 1)

荷物の準備とかを慌てて始め、無事到着。

初日は集中開発コースの人々も全員共通の講義を受けるので、Cコンパイラには一切手を付けなかった。(同じテーブルにいる人に対してコンパイラのdemoとかをしたりはした。)共通講義では様々な有益情報が得られ、ありがたかった*3

Day 2

集中開発である。とりあえずはコードを書いていく。

具体的な開発内容は前述の日記に書くとして、大まかな変更点は以下の3つ。

  • 過去に発見していたものの見なかったことにしていた「関数呼び出し式の中で関数呼び出しが行われるとバグることがある」を修正した。このバグの原因究明で私が悩んでいる間、隣のほうでは「レジスタに整数演算するとセグフォする」とかいうすごい現象が起きていて、「つらそう(つらそう)」などと思った。
  • 構文解析と意味解析を分離した。時間は掛かったが、わりとただの作業だったのでそこまで大変ではなかった。
  • で、旧い「構文+意味解析機」と新しい「意味解析機」で生成される構文木にわずかに差がある(ので、新しい構文木でテストを走らせるととあるテストケースで無限ループになる)問題が検出された。要するに新しいほうがバグってる。経験上、デバッグは睡眠時間を削って行うと効率が下がるので、そこそこ原因が絞り込めた段階で寝ることにした。

Day 3

朝起きてデバッグ。後置++と後置--に関して意味解析器でノード付け忘れていただけだった。なるほど、それでforの第三式が吹っ飛ぶので無限ループになっていたと。

とりあえず構造体が欲しい。オフセットの計算が少々厄介という話を聞いた*4ので、これを機にそのあたりについてちゃんと勉強しようと考えた。とりあえず軽くググって出てきた「データ型のアラインメントとは何か,なぜ必要なのか?」に言及したところ、「そのサイト分かりやすかったからオススメ」と言われたので読んだ。

理解したので組んでいく。「このパターンは使わんから組まなくていいか」みたいなアドホックをやりまくっていたら泥沼にはまり、作り直すはめになった。型のスコープをちゃんと実装するのが面倒ということも鑑み、最終的に取った戦略は次の通り。

  • ちゃんとtype-specifierとして struct A {int a; int b;} と struct Aの両方を許す
  • 変数宣言/定義およびそれに準ずるものが登場した時、その宣言の中に構造体の言及があるかどうか確認。{int a; int b;}つきで構造体型が言及されていればメンバを登録。ただしローカルの場合は現状では未対応。{int a; int b;}なしでの言及はローカルでも問題なく素通り。

この「登録」でstructのsizeを追加していくことで、structの配列とかをローカルやグローバルに作れるようになった。 

Day 4

構造体だけあってもメンバアクセスができなければほぼ意味がない。ということで実装した。

実質今日が最終日なので、とりあえずあと一つぐらい機能を足して「ここまでできました」をやりたい。何を足そう。

そういえばvoidが一切無いな。で、voidを足せばvector.cがセルフホストできそうだ*5。足すか。

ということで「空の引数リストとしてのvoid」「void*としてのvoid」「何も返さない関数としてのvoid」を実装。スライド(↓*6)も作って発表した。

docs.google.com 

そのあとは、本を頂いたりした。私はそこそこ前に買おうかと思って結局買わないでいた 「https://www.amazon.co.jp/独自CPU開発で学ぶコンピュータのしくみ-伊藤-剛浩/dp/4798045365」などを頂いた。

Day 5

最終日。とりあえず、増えた荷物*7を処理するのが大変だった。

 

様々な発表が行われた。uint256_t氏の「自作JSエンジンの、自作ブラウザ上での発表」のインパクトがとても強かった。

0日目セルフホストの人がプレゼンも2分スピーチも完璧にやっていて「すごいなぁ」となった。

一方私は発表の合間を縫ってswitch-caseを実装することを試みた。結果、caseもdefaultもないswitch *8が実装できた。

 

「さてセキュキャン終わったし帰るか」とか思っていたら、講師のRuiさんの「Cコンパイラ班で焼肉食べませんか、よかったらuint256_tくんもどうぞ」という提案により焼肉となった。

 Ruiさんごちそうさまでした。ありがとうございました。(DMに書き忘れたのでここに書いておく)

その後

なんやかんや機能を足していってセルフホストできた。やったね。

…というので締めるのも微妙なので、セキュキャン以降セルフホストまでの話をかいつまんで書いて締めとする。

フランケンコンパイル

順当に機能を足していき、8月26日には音ゲー勢とのオフ会で(人々が音ゲーをしている中、私はゲーセンの椅子に座ってコードを書いていたので)フランケンコンパイル*9に成功した。

はすじょいさんと椅子で待機してる

— 南夏 (@t_blazerona) 2018年8月26日

なお、余談だが、フランケンコンパイルに浮かれていたらあえなくバッテリー切れを起こした。

隣でブチッを見て驚いた

— 南夏 (@t_blazerona) 2018年8月26日 

一回復活しかけたけどゲージ真ん中で再度ブチッ
悲しいなぁ

— 南夏 (@t_blazerona) 2018年8月26日

 最近パソコンのバッテリーが不安定でつらい思いをしている。

diff

先人の轍を踏まぬよう、今の段階から2世代目(clangでコンパイルした初代自作コンパイラで、自作コンパイラの一部をコンパイルしてできたバイナリ)と3世代目(2代目の自作コンパイラで、自作コンパイラの一部をコンパイルしてできたバイナリ)のdiffを取るようにしておいた。これが実はあとでかなり役立った。

構造体を値で返す

セキュキャンCコンパイラ班の他の人のコンパイラと私のコンパイラの大きな違いとして、私の実装では構造体を値で扱っているコードがかなり多いというのがある。ということで8月28日の段階で構造体の一括代入を実装した。

さて、「構造体を値で扱っているコードがかなり多い」ということは、構造体を値で返す関数も実装する必要がある。ところで、(私がソースコード中で使っている構造体については)x86-64 System V ABIでは2種*10に分類され、挙動が異なる。

 とりあえず、INTEGERという分類に属する構造体を返せるようにして、

 関数に構造体を渡すことに関しては実装しないことにして*11

「INTEGERに属する構造体を返す関数」を呼ぶ処理を書いたら興味深いタイプのバグが出てきて、

 MEMORYに属する構造体を返す関数の呼び出しを実装し、

 MEMORYに属する構造体を返す関数の定義を実装し、

冒頭のとおりになるという話である。

 

*1:応募用紙はまた別の機会に全文公開する

*2:荷物の準備、とかではないことに注目

*3:講義の内容にどこまで言及していいのかが分からないので、何も言及しないでおく

*4:「int, char, char, intって12バイトなんですよ。『intが4バイトだから全部4バイトに合わせて、intが4バイト、charが4バイト、charが4バイト、intが4バイト』みたいに勘違いすると間違える」みたいな話がされていた

*5:constとか無いからそれは手で取り除かないと動かないけど

*6:ただしセキュキャン時とのdiffあり

*7:様々なものを頂けたのはありがたいことであって、どちらかというと私の用意したスーツケースが妙に小さかったことが主問題であった

*8:「それはswitchではない」と言われた。それはそう

*9:コンパイラの一部を自分のコンパイラで、残りをclangでコンパイルすること

*10:詳しいことは https://www.uclibc.org/docs/psABI-x86_64.pdfに書いてある

*11:まず、MEMORYに属する構造体はスタックを使わなければならない。現状引数をスタックで渡す処理は実装していないので後回しにしたい。また、INTEGERに属する構造体はレジスタを使って渡すのだが、ソースコード上の引数と消費するレジスタ数が異なってくるとか面倒なので、こっちも後回しにしたい。