gcc-4.7のAdaの変更点

公式Release NoteのAdaのところが相変わらず手抜きなので、いつものようにメモっときます。
今回はなぜかAdaCoreが本気出してて、gccがStage 3(バグ修正のみのフェイズ)に入ってもガンガンコミットされてたりしてたので、最後まで追いきれてないです。ウォッチャーするのも根性がいるのです。
なお相変わらず、Bugzillaは放置されてます。AdaCoreはgccのBugzilla使ってないのでしょうか?
D言語(のように開発が他で活発に行われているフロントエンド)がgccリポジトリに入っても同じような状況になるんでしょうか?gccの開発体制とか全くわかりません!コミュ障です!

Ada2012対応

use all typeが部分的に使えます。
といっても変数を初期化する式でだけです。(V : T := X;のXでだけ。V := X;のXはダメでした。)
早く全面的に使いたいですねー。

aliased引数が使えます。

aspectのみの機能も実装されてきています……って書くと分かりづらいですね。
Ada2012のaspectC++0xで言う属性です。__attribute__(())です。Ada2005までは全部pragmaで後置していたのですが、宣言と一緒に書けるようにしたものです。
構文自体は4.6から受け付けるようにはなっていたのですが、既にpragmaとして存在する機能のみの対応でした。
4.7では、aspectの形でしか存在しない新機能にも対応しています……ってことですハイ。

Implicit_Dereferenceが作れるようになりました。
C++で言うと追加データやデストラクタも持てる参照型です。あるいは暗黙のキャスト演算子をひとつだけ持てるstruct。
(以前試したときは関数呼び出しの返値でのみ解釈されてましたが、オブジェクト単体でも解釈されるようになってます。やったー。)

range-based for(違)も実装されました。
Ada.Iterator_Interfacesもちゃんとあります。
展開されたコードを見ると冗長だなあ……と思いますが、Iteratorの型がForward_Iterator'Class(要するにサイズ不定の型)でFirstもNextも仮想関数で、Referenceの返値もデストラクタを持ってることを考えますと短くなりようもないですしね。
(-O3でコンパイルして、ループごとに仮想関数×1、非仮想関数×2の呼び出しが追加で必要。そんなもん?)

operator [] (違)も実装されました。
規格では右辺値ならConstant_Referenceが呼ばれるみたいなことが書いてある気がするのですがConstant_Reference/Referenceの呼び分けはコンテナ自体がconstant(in)かどうかしか見てないようです。
現在のAda.Containersの実装ではConstant_ReferenceとReferenceに差が無いので関係無いです。
俺々コンテナを使うときはReferenceが呼ばれることによるパフォーマンス悪化に注意、でしょうか。
(以前試したときはConstant_Referenceも「参照型」を返す関数のみ受け付けてましたが、Iterator_Elementの型をそのまま返す関数も受け付けてくれるようになってます。)
あと2引数以上の時の挙動はバグっぽいです。

QueueとかMultiway_Treeやなんかのコンテナも実装されました。

Synchronous_Barriersやなんかの同期オブジェクトも実装されました。

Subpoolも実装されました。
libcで言えばmalloc_zoneみたいなやつです。
もちろんmallocで取ってきたメモリを独自管理してますので、malloc_create_zoneやHeapCreateを直接使うのと比べて二重管理ry

その他改善点

Controlled型(C++風に言うとデストラクタのある型)がリンクリストを作らなくなってます。
以前は同一スコープのControlled型はリンクリストでつながってて、スコープを抜けるときはリストを辿って終了処理が行われていました。
つまりオブジェクト毎にポインタ2つ分の余計な領域が取られていたわけですが、これが無くなりました。
でもリンクリスト自体が無くなったわけではなく、スコープごとにFinalization_Masterなるものが作られるようになって、これがリンクリストを持っています。要するに侵入型リンクリストが外付け型になっただけ。
なので依然としてBoehm GCを使ってもControlled型は自動では解放されません。

Unbounded_Stringのデータが参照カウンタで共有されるようになりました。
(以前試したときはデフォルト無効だったのですが、デフォルト有効になってました。めでたしめでたし。何度も書いてますようにネイティブで可変長文字列を持つ言語と比べるとやっぱり見劣りしますが、とりあえずstd::stringを見上げることはなくなりました。)

protected型がread-write lockしてくれるようになってます。
今まではprotected procedureもprotected functionも同じ排他制御がなされてたのですが、protected procedureはwrite lock、protected functionはread lockされるようになりました。
(ここでfunctionは副作用なしというスタイルを心がけて云々と言えないのが辛いところです。Ada2012ではfunctionもin out引数を持てるようになって、副作用のあるfunctionの利用が加速しています。世の中が関数型に傾倒しているというのに、昔っからfunctionの引数がin固定だったりpackageがpureであることを明示できたりしてて素地があったはずのAdaは見事に逆行してます。)

他のフロントエンドから投げられたも受け止められるようになっています。
System.Exceptions.Foreign_Exceptionにマップされるようです。
これでミックスランゲージも大丈夫!
(以前、他のフロントエンドから投げられた例外はwhen othersでは受け止められないみたいなことを書きましたが、一年持たずに嘘記事になりました、めでたしめでたし。)

Formal_コンテナが追加されています。
Bounded_コンテナの実行時エラーチェック強化版?

MKS単位系付きの書式化出力ルーチン群が追加されています。
よくわかりません!

他にも新pragmaとかいっぱいありそうですが把握できてません!

4.7がStage 3に入ったので試してみた

  • 2012対応状況
    • use all type → 「変数 := 式;」の形の式の部分でのみOK。
    • Implicit_Dereference → 関数呼び出しの返値でのみOK。
    • range-based for(違) → OK、ありえんぐらい酷いコードが出てくるけど動く。Ada.Iterator_Interfacesもちゃんとある。
    • operator () (違) → OK、Constant_Referenceが呼ばれないような気もするけど。
    • 後は4.6のときと同じ。
  • その他気になる改善点
    • protected型がちゃんとread-write lockしてくれるようになってます。
    • Controlled型がリンクリストを作らなくなってます。*1
    • Unbounded_Stringに参照カウンタの実装も追加されたみたいです。*2でもデフォルトでは無効っぽい。

*1:上では余計なデータはVMTだけみたいに書いてますが、実はgcc 4.6までは前後へのリンクも持ってました。

*2:上ではあたかもUnbounded_Stringが参照カウンタで実装されてるように書いてますが、gcc 4.6までは毎回コピーされてました。

Delphiの文字列並の効率を実現できるようにUnbounded_Stringを改造したい

何度も書いてますが、言語組み込みの文字列とライブラリ提供の文字列では効率に天と地の差があります。C++みたいになるべく自然に使えるように努力している言語ですら、その差は到底埋められていません。ましてやAdaのUnbounded_Stringなんて(略)ですよ。
Delphiの文字列のスゴイところと、どうすればおんなじ風にできるかなー、ってのをダラダラやってた話です。あ、ここでいうDelphiの文字列というのは2007以前のAnsiStringのことです。2009以降については正直静的言語なのにコードページを実行時に持つ必要は無いと……どうでもいい。
実はD言語の文字列のほうがもっとスゴイのですが、あのスゴさはGC前提なので省略。

Delphi2〜Delphi2007のAnsiStringの構造

「参照カウンタ、長さ、文字列本体、(C互換用のゼロ終端←以後省略)」というメモリブロックへの「ポインタ」です。
まあこの構造自体はstd::stringでも同じでしょう。0xでは参照カウンタでの共有は許されなくなったんでしたっけ?
で、早速Adaには難点が。Adaでは(C++で言う)デストラクタを実現するためには、Ada.Finalization.Controlledを継承しなければいけないのですが、そうすると当然VMTが必要になるわけです。つまりポインタ2つの組になってしまうわけです。若干メモリの無駄。

Delphi C++ Ada
×

レジスタに入る

AnsiStringの実体はポインタ1つだけです。ということはレジスタに入ります。
std::string(C++03以前)も、実体はポインタ1つだけにできますが、レジスタに入りません。メンバ関数にthis(ポインタを1個だけ持つクラスへの更にポインタ)を渡さないといけないからですね。*1
もちろん上記のようにAdaでは最低でもポインタ2つ必要になりますので、レジスタには入りません。

Delphi C++ Ada
× ×

文字列定数

AnsiStringの定数は、静的に確保されてます。参照カウンタには-1が入ってます。コンパイラが最初っから文字列の構造を把握してるからこそですね。
一方、std::stringもUnbounded_Stringも、コンストラクタ/To_Unbounded_Stringに定数文字列を渡されても、動的にメモリを確保して中身をコピーする必要があります。C/C++もAdaも文字列定数は単なる文字型の配列であり、参照カウンタも長さデータも持ってないからです。

そのため、ちょっと構造を変えましょう。

「参照カウンタ、長さ、文字列本体」→「参照カウンタ、長さ、文字列本体へのポインタ、(文字列本体)」

動的に確保する時は、「文字列本体」まで一緒に確保して、「文字列本体へのポインタ」メンバには「文字列本体」(要するにすぐ次のアドレス)を入れます。
静的に確保する時は、「文字列本体」は確保せずに、「文字列本体へのポインタ」メンバには実定数へのポインタをそのまま持たせます。
……実はこれだけだとダメです。
仮に引数の型をchar const *やaccess constant Stringにしたとしても、ローカル変数(定数)へのポインタを渡されたりすると、そのまま使うわけにいかないですよね。呼び出し元の関数を抜けると消えちゃいますから。
というわけで安全に文字列定数を作るためには、引数で渡された定数が本当に定数であることの保証が必要になります。D言語ならimmutableって便利なものがあるのですが、C++やAdaにはありません。
ただAdaのaccess型(ポインタ)はスコープに紐付けられた型ですので、ライブラリレベル(一番外)のaccess型を使えば、関数を抜けるなどで消えたりはしないことだけは保証できます。それでも、Adaのaccess constantはC++のconst *と同じで、実際に指しているものは変数かもしれません。中身が書き換わる恐れだけは無くせません。

Delphi C++ Ada
× ×

引数渡し

AnsiStringは参照カウンタで管理されていますが、引数渡しの時は参照カウンタが増減されません。
実引数は関数呼び出しよりも寿命が長いことが確実だからです。
C++やAdaでこれを避けるには参照渡しにするしかなくて、そうするとポインタのポインタになってしまって、無意味な逆参照が1段入ってしまいます。引数として渡ってくるわけですからどんなにコンパイラが賢くてもひとつの関数内だけの最適化では取り除けません。LTOなら話は別。
まあAdaは特に、継承を使っている型は明示しなくても問答無用で参照渡しになってしまうわけですが。

Delphi C++ Ada
× ×

世の中にはStringPieceなんかもありますが、折角の参照カウンタが活用されないため、あんまり意味無いかなあ。(リンク先の例だと中でCopyToStringするのも呼び出し元で一時的なstd::stringを作って中では参照カウンタを増やすだけってのも大して変わらないはず?out->append(dir);をout->assign(dir);に変えたら……と思って試してるのですが……続きは↓↓)

返値

返値も今までの話と似たようなものです。
AnsiStringレジスタで返せて、その際参照カウントも増減しません。
std::stringはレジスタでは返せずに(暗黙の引数が必要になる)、コピーコンストラクタと一時変数のデストラクタも走ります。(0xならムーブコンストラクタと一時変数のデストラクタ)
Unbounded_Stringもレジスタでは返せずに(暗黙の略)、やっぱりAssignと一時変数のFinalizeが走ります。
もちろん定数を返すとわかっているなら参照返しにすればよいのですが、std::stringやUnbounded_Stringは上記のように定数であっても作成時点で中身全部コピーしたりしますので参照返しにして参照カウンタの操作を省略できたやったー、なんてやっても虚しいだけです。最終的に単なるポインタのレジスタ間コピーだけになるAnsiStringだからこそ活きる最適化ですよね。

Delphi C++ Ada
× ×

連結

文字列を一気に3つ連結したりする時も、AnsiStringならコンパイラが知ってますからまとめて連結してくれたりします。(ランタイムには_LStrCat3とかある)
std::stringやUnbounded_Stringの連結は、演算子オーバーロードで実現されいるわけですが、「A & B & Cを一括で処理するオーバーロード」は定義できませんので、ちまちま連結していくしかないです。
ただ少しだけあがくことは出来て、「A & B」の時点で領域を多めにとっておいて、「& C」がそこに入るなら再アロケート無し、みたいな頑張り方はできます。もちろん「& C」されなれば無駄な領域ですのでふつーの発想だとやらないと思います。後はRopeにするぐらいかなあ。

Delphi C++ Ada
× ×

というわけで結論

Unbounded_Stringをいろいろ弄ってたんですがあまり改善できそうにないです。
要は失敗談でした。

*1:ただしC++Builderには__declspec(delphireturn)なるものがありましてレジスタに入れることができます。

ところで

http://www.ada-auth.org/standards/12rm/html/RM-A.html

を眺めていて、Wide_やWide_Wide_バージョンが必要な大抵のpackageはAda.直下にWide_なんとかがあるのですが、StringsだけAda.Strings.Wide_なんとかの形になる(他と同じならAda.Wide_Strings.なんとかになるはず)のが気になって夜も眠れないのは私だけでしょうか?

Ada2012で追加される文字判定サブプログラム

http://www.ada-auth.org/standards/12aarm/html/AA-A-3-2.html

Is_Line_Terminator, Is_Mark, Is_Other_Format, Is_Punctuation_Connector, Is_Spaceが追加されます。

Unicodeカテゴリともちょっと違うようですし(ていうか素直にIs_XXXをUnicodeカテゴリの全種類分並べるだけではなぜダメだったのか)、下の方の説明を読んでもいまいちピンと来ないのですが、{AI05-0185-1}のリンクをたどるとこの正体が明らかになります。

The current version of the GNAT compiler has defined the following implementation-defined packages;
http://www.ada-auth.org/cgi-bin/cvsweb.cgi/ai05s/ai05-0185-1.txt?rev=1.7

まじか……。
文字コードに対するAdaCoreのナンセンスさ加減はcomp.lang.adaでもある程度共有された認識だと信じていたのですが。
ということで、a-zchuni.adsを読むとこれらのサブプログラムの正体がわかります。

サブプログラム 対応するUnicodeカテゴリ
Is_Line_Terminator Zp(Paragraph_Separator), Zs(Space_Separator), 0a-0d(LF, VT, FF, CR)
Is_Mark Mn(Nonspacing_Mark), Mc(Spacing_Mark) *1
Is_Other(_Format) Cf(Format)
Is_Punctuation(_Connector) Pc(Connector_Punctuation) *2
Is_Space Zs(Space_Separator)

名前ぐらいUnicodeカテゴリを正確に反映しろよ……っていうか私Unicode詳しくないんですが、MnとMcだけを判定したくてMeを除きたいような用途ってなんかあるんでしょうか?詳しい人がいましたら教えて下さい、お願いします。
流し読みですが、Is_Punctuation_ConnectorのほうはRandy大先生が_Connectorを追加させた様子。a-zchuni.adsのIs_Punctuationのままですと、P系カテゴリ全部含むと誤解しちゃいますもんね。とりあえずその程度には元のa-zchuni.adsは酷い。

で、Handlingのサブプログラムは、Maps.Constantsのほうに対応する集合が定義されてるはずなんですが、まだ追加されてないです。
http://www.ada-auth.org/standards/12aarm/html/AA-A-4-6.html

要するに他のベンダーはこんなの実装せずに無視しろってことですよねわかります。

*1:Me(Enclosing_Mark)は含まれない

*2:Pd, Ps, Pe, Poは含まれない

たまにはOOP

定期的にAda 95のRationaleを読み返したくなります。

http://web.archive.org/web/20020607043031/http://www.tsujiken.ee.kogakuin.ac.jp/jada95_02.html

このRectangle(四角)からCuboid(立方体)を派生させる例、OOPの入門としては犬猫動物並によくあると思うのですが、やっぱ間違ってますよねー。
そもそも2次元のものと3次元のものを混ぜていいのかというのをさて置いて全部3次元空間上のオブジェクトとして考えると、RectangleはCuboidの特殊な場合(Height = 0固定)ですので、Rectangle is a Cuboidですよねー。
でもRectangleにHeightを持たせたくないのは確かですので、こう。

type Any_Cuboid is abstract tagged
   record
      Length, Width : Float;
   end record;
function Get_Height (Item : Any_Cuboid) return Float is abstract;

type Rectangle is new Any_Cuboid with null record;
overriding function Get_Height (Item : Any_Cuboid) return Float is (0.0);

type Cuboid is new Any_Cuboid with
   record
      Height : Float;
   end record;
overriding function Get_Height (Item : Any_Cuboid) return Float is (Item.Height);

この手の逆転の間違いを犯してるのは結構あって、例えばimmutableなNSStringからmutableなNSMutableStringが派生してるとか……。(immutableと確信できる継承階層が無いので毎回copyしないとけない……どうせデータ本体はCOWで共有されてると思うけど←想像)

作りかけの置換ランタイム

本当は完成させてから偉そうに書きたかったのですが、もうなんといいますか完全に私事で自業自得ですがそれどころではありませんので、書いちゃいます。

https://github.com/ytomino/drake

今までGNATについてあーだこーだ文句を書いたりコンパイラに勝手なパッチを当てて配布してたりしたのですが、そうした成果をひとつにまとめてランタイムに仕立ててしまおうという、私の誰得Ada趣味が極まったプロジェクトです。トータルで考えると何人年かけているのか自分でも怖い……。
D言語Phobosに対するTangoのような存在です。
はいあれだけ期待されていたTangoの末路を見ればこれがどうなるかも予想できますね?

……完全に私が自分で使うためだけに作っているのですが、それでも見ていただけると嬉しいので書き残しておきます。

続きを読む