mcsの最新情報と今後の展望について

C# Advent Calendar11日目は、C#開発環境の異界、Monoプロジェクト方面からお送りします。って10日目もそうだったみたいですが(しかもとっかかりなのにInternalCallとか、けっこう踏み込んでる!)

Monoを取り巻く環境は、この1年間で大きく変わりました。Microsoftは大々的に.NETをオープンソースで公開する方向に向かい、コミットし続けていて、4月にはC#VBコンパイラー インフラストラクチャーであるRoslynが、11月には.NET Frameworkの(一部の)ソースコードと、新しいモジュール指向のフレームワーク再編成となる.NET Coreが、それぞれ公開されました。.NET Coreのソースコード公開にはRyuJITも含まれるということで、これが実現すると、ランタイム、コンパイラ、クラスライブラリのフレームワーク3大要素がすべてオープンソース化されることになります。

そういった状況下にあって、もともと.NET Frameworkオープンソースで実装していくという起点から始まったMonoプロジェクトはどうなっていくのか、ということは当然ながらさまざまな人が疑問に思うところでしょうが、一言でいえば「.NETのうまいところを取り込みながら継続して開発されていく」、ということになります。その辺については、何やら来月行われるらしいGoAzureというイベントでちょろっと(50分くらい)しゃべりますんで、興味のある方はぜひ聞きに来てやってください。


Japan Azure User Group 【リニューアル準備中】 GoAzure 開催決定

さて今日はC# CalendarなのでC#コンパイラmcsの最新情報…と書くと無駄にカコイイですが、まあ現状ということですね…について(のみ)書こうと思います。まあ、最新情報だけだと知らない人(ほとんど)が光の速さで置いてきぼりになるので、適宜、過去を振り返りつつ…

mcsの変遷

mcsは、monoのC#コンパイラであり、monoの一部に含まれています。monoのソースツリーには、トップレベルに "mcs" というディレクトリが存在していますが、この中にはC#コンパイラであるmcsと、C#で書かれたクラスライブラリが存在しています。この一見妙なディレクトリ構成は、歴史的事情によります。もともと、monoのソースコードはCで書かれたランタイムを中心とする "mono" と、C#で書かれたコンパイラおよびライブラリを中心とする "mcs" の、2つのCVSリポジトリから成っていました。やがてSubversionに移行し、その後さらにgithubに移行する時点で、この "mono" の中に "mcs" を取り込むことになったのです(そうしないと2つのリポジトリの間でバージョンを合わせるのが面倒だったので)。

mcsは、開発当初からC#で書かれていました(MicrosoftcscC++だそうです)。当初はmcsでmcsをビルドすることは出来なかったので、Microsoftcscを使ってビルドしていました。もっとも、Monoのバージョン1.0が出る以前に、セルフホスト(自分で自分をビルド)出来るようにはなっていました。

mcsには、歴史的には特定の.NET Frameworkのバージョンに対応する、いくつかの派生物がありました(.NET 2.0に対応するgmcs、.NET 4.0に対応するdmcs、Silverlightやモバイルのプロファイル「.NET 2.1」に対応するsmcs)が、現在ではmcsで統一されており、gmcsやdmcsといったUnixスクリプトは、内部的にはmcs -langversion:* で代用されています。昔は実行バイナリそのものが異なっていましたが、それはmcsがSystem.Reflection.EmitのAPIを使用していて、このAPIでは(mcs自身を)実行中のmscorlibに対応するバージョンのILしか生成できなかったためです。現在では、IKVM.Reflectionというライブラリを使って(Cecilのようなものですが、APIはSystem.Reflection.Emitと概ね同等です)、この制約を受けることなくILバイナリを生成しています。

mcsのソースコードには、コンパイラスタンドアローン テストが含まれており、mcs/testsには正常系の、mcs/errorsには異常系の、テストが大量に存在します。

C# 6のサポート

mcsの開発は、Roslynが公開された後の現在でも、継続して行われています。githubにあるmasterブランチのmonoに含まれるmcsでは、C# 6.0の機能の多くが実装されています(C# 5までの機能は完成しています)。具体的には

  • indexed initializers
  • auto-property initializers
  • static using statements
  • nameof operator
  • async in catch / finally
  • expression-bodied members
  • null propagation
  • exception filters

あたりは実装されているようです。ていうかこれで全部じゃね? 上記以外だと、わたしの知る範囲では、string interpolationがC# 6仕様上どうなったのか、だけが気になるところです。mcs(C#コンパイラ)は、昨今ではかなり早い段階で新機能を実装できています。Microsoftが新機能の正式版をリリースする頃には、実装は終わっています。

しかも、以下のような、「C# 6に入れるつもりだったけど入れなかった」構文もちゃっかり実装されていて、ちゃんと「C#6にその機能はない」と言ってエラーにしてくれます。

  • declaration expression
  • primary constructor

ちなみにmasterのmcsでは、C# 6の機能はデフォルトで有効になっています。かつては -langversion:future というコマンドライン オプションを必要としていましたが、このオプション値はdeprecatedとなりました(futureのみです。-langversionそのものは有効)。masterにあるものに、これは必要ないということでしょう。これは、monodevelopC#プロジェクトを作った場合でも、対象フレームワークが.NET 4.5(以降)であれば、-langversionオプションが追加されず、これらの新機能がデフォルトで有効になる、ということでもあります。

Compiler as a Serviceとしてのmcs

mcsは、最初からC#で書かれていたので、Mono.CSharpというライブラリとして、他の.NETアプリケーションに組み込んで使うことが可能でした。

Mono.CSharpの利用例として、最初はC# shellというREPLコマンドラインツールが開発されました。monoがインストールされた環境であれば、csharp.exeもインストールされていると思います。このREPL機能は、今年になってXamarinがXamarin Sketchesという「インタラクティブ」機能を発表したことで、再度脚光を浴びたのではないかと思います。Xamarin Test Cloudにもインスタントにテストを書いて実行する機能が追加されて発表されましたね。csharpシェル、ちまちましたことを動作確認するのに便利です。わたしも「C#6の新機能どこまで使えるのかなー」とか確認するのに使ったばっかですしね。

Mono.CSharpの応用にはもうひとつ、NRefactoryというライブラリがあります。これは、オン ザ フライでソースコードを解析して結果を取得し、またソースを加工できる、いわゆるいわゆるCompiler as a Service、あるいはLanguage Serviceと呼ばれる機能を実現するものです。Roslynも本来的にはこれを実現するものです。NRefactoryとRoslynの関係については、後で詳しく書きます。

Roslynとmcs.exe

Monoチームは、Roslynのオープンソース化を歓迎しています。将来的にRoslyn単一のソースで.NET開発が.NET Framework上でもMono上でも行えるようになれば、開発コストもいろいろ下がることでしょう。

ただ、Roslynへの切り替えは、あくまで将来的な展望であって、今すぐにできることではありません。ひとつにはコンパイル速度の問題があります。mcsは何だかんだでもう10年以上の歴史があって、だいぶリソースが限られていた頃から、要所要所で最適化してきたコンパイラーです。今ではある程度改善されているかもしれませんが、Roslyn公開当初のコンパイラーはmcsの10倍遅かったそうです。

(個人的に覚えているのは、わたしがmcsをハックしていた時期がちょっとだけあって、mcsがまともに行・列番号をレポートしてくれないので、イチから実装し直しました。そのために最初はIXmlLineInfoみたいな構造を作って持っていたのですが、そのやり方にストップがかかります。「そのやり方だと重くなる。int1つにして、そこにファイルインデックス・行・列を保存してくれ。」 当時は「まじか…!!」と思いましたが、実際ソースは大量のトークンから成っているわけで、そうするとメモリ消費コストがだいぶ下がったんですね…。そんなわけで、今でも列番号は254が最大値です。)

そんなわけで、現状、コンパイラーとしてのmcsを置き換えることはありません。mcsはmonoの中でも割と優秀な部分で、フロー解析はわたしの知る限り完璧だし、エラーや警告のレポートもcscとほぼ違いはなく、言語の最新版にも追従していて、文句なく使える部分なので、これをRoslynで置き換えるユーザーレベルでのメリットは、ほとんど無いでしょう。

RoslynとNRefactory

ではRoslynは全然使われていないのかというと、そういうわけではありません。Roslynにはコンパイラー インフラストラクチャーとして、Compiler as a ServiceあるいはLanguage Serviceを提供している側面があります(というかそれが主要な機能ですが)。これはMonoDevelopSharpDevelopにおけるNRefactoryに相当する部分です。

XamarinでMonoDevelopとNRefactoryを開発しているハッカーが、NRefacotoryに "roslyn" および "roslyn-mono" というブランチを作って、そこにRoslynをNRefactoryに「取り込んだ」バージョンを公開しています。

https://github.com/ICSharpCode/NRefactory/tree/roslyn

…あれ? 取り込み? 置き換えじゃないの? と思われるかもしれませんが、RoslynはまだCompiler as a Serviceを実現するためのコア部分をブラッシュアップしている段階です…と書くと明快でないので、軽く説明しましょう。と言っても、2年前に同じ話を書いたことがあるので、その焼き直しということになりますが…

NRefactoryでもRoslynでも、その機能は(1)基本的な機能と(2)応用的な機能に大別できます(もっと細かくもできますが、今はとりあえず2つ)。編集中のソースコードに対して、オン ザ フライで解析をかけて、コンパイルエラーなどを報告する機能は必須(1)です。そうではなく、解析したソースコードの内容をもとに、変数名の変更(セマンティックに同一の識別子をもつ変数参照の変更を伴う)や、解析結果から一定のパターンを認識してソースを加工するリファクタリング機能(NRefactoryではCode Actionsと呼ばれます。Code Issuesでも加工機能があるかも)は、どちらかというとアドオン的なもので、(2)必須ではありません。

Roslynは、その基盤を安定させた上で、追加機能を実現しなければなりません(もうVS2015のリリースを意識した開発過程に入っているわけですし)。そのため、NRefactoryで実装されている大量のソース分析の機能が、実はRoslynにはあまり実装されていませんし、まだその実装に大量の開発リソースを費やすタイミングでもないのです。というわけで、NRefactory側としては、Roslynはあくまで一部を「取り込む」かたちで利用しています。

そして、NRefactoryのroslynブランチは、まだmonodevelop本体では利用されていません。これも、今すぐ取り込めるわけではないので、NRefactoryにRoslynを馴染ませながら、何らかの時点でそのブランチに切り替えることになるのではないかと思います(わたしの予測です)。NRefactoryとRoslynは、APIにはそれなりの違いがあり、MonoDevelopのtype system databaseはNRefactoryベースで作られているので、単純に「Roslynそのもの」への置き換えが効くわけでもありません。

もっとも、MonoDevelopで現状使われているNRefactoryでも、上記(2)の部分については、まだこれから実装の品質を改善していく必要があるところです。次のMonoDevelop 5.8では、Source Analysisがデフォルトで有効になる見通しですが、実は内部でわたしが個人的にクラッシュを頻繁に経験していて「もうちょっと待ってほしい」と抵抗した部分でもあります。(問題のあるコードを少し直してもらい、クラッシュはだいぶ減ったので、デフォルト有効にすることに同意しました。)

じゃあ早めにRoslynに移行しちゃったほうがいいんじゃね?と思われるかもしれませんが、問題の原点がパフォーマンスにある部分が多く、そうするとRoslynでは(現状)さらに状況が悪くなるので、やはりこれもVS2015に伴うRoslyn安定版のリリースに続く(と思いたい)最適化を待ったほうが良いところかなあ、といったところです。Visual Studio上でも、(重くなるとは言われるものの)現状でそれなりのパフォーマンスで動作している(であろう)ReSharperには、まだアドバンテージがあるかもしれませんね。

総括

以上のように、Roslynのオープンソース化が発表された後でも、monoはmcsから直ちにRoslynに移行できる状態ではなく、Roslynの今後の進化に期待しつつ、NRefactoryから準備をしていって、いずれは切り替える、という前提で、mcsの開発を継続していくことになります。幸い、mcsはもともとC#最新版への追従も早く、実装も安定しているので、しばらくはこのまま特に問題なく続いていくのではないかと思います。