はじめに
D言語の公式コンパイラである dmd が更新され、最新バージョンである 2.093.0
が 2020/07/7 にリリースされました。
今回は比較的控えめのアップデートなので、リリースノートの内容全種紹介していきたいと思います。
※ 2020/07/12追記 ベータが取れて正式版になったところ、改善点がだいぶ増えたので抜粋に戻します
より細かい内容は下記リンクからChangeLogをご覧ください。
- ChangeLog
また、前回5月ののリリースまとめは以下になります。
- D言語の更新まとめ 2020年5月版(dmd 2.092.0)
変更点目次
コンパイラ変更点
- ビルトインの
__traits(isCopyable, T)
が追加されました - インターフェースの実装が適切にチェックされるようになりました
-
shared
変数の初期化が許可されるようになりました - templateの統計を収集、一覧化するための
-vtemplates
スイッチが追加されました
コンパイラ強化点
※2020/07/12 追記
- Bugzilla 20791:
extern(C++)
が末尾のコンマを許容するようになりました - Bugzilla 20796:
package(x.y)
形式の保護属性が宣言と同じモジュールを許容するようになりました
ランタイム変更点
-
core.sys.linux.unistd
にexit_group
が追加されました - 新たなモジュールとして
core.sys.darwin.mach.nlist
とcore.sys.darwin.mach.stab
が追加されました -
object.selector
が非推奨になりました - メモリ関連のGCオプションがより高精度で指定できるようになりました
ランタイム強化点
※2020/07/12 追記
- Bugzilla 20746: ガベージコレクタで使われる LCG(線形合同法) で状態bitのうち48bitしか使われなかったものが64bitすべてを使うようになります
- Bugzilla 20787:
core.sys.darwin.sys.attr
が追加されました - Bugzilla 20844:
cas
の呼び出し後、データのアラインメントを考慮するようになります - Bugzilla 20859:
ReadWriteMutex
のtryLock
にタイムアウトを指定できるようになりました
ライブラリ変更点
ライブラリ強化点
- Bugzilla 19525:
Duration
に対するstd.algorithm.sum
の引数なし呼び出し時エラーが改善されました - Bugzilla 20496:
chunkBy
がref
引数による述語をサポートします - Bugzilla 20869:
std.algorithm.mutation
のmove
がonPostMove
の属性を考慮するようになりました - Bugzilla 20887: 配列でないレンジに対する
std.digest.digest!(Hash, Range)
の速度が改善されました
インストーラー変更点
- インストールスクリプトが Windows のコマンドプロンプトで動作するようになりました
DUB変更点
- ルートパッケージのターゲットに関する環境変数が追加されました
- CLIの拡張性が改善されました
ツール変更点
ツール強化点
※2020/07/12 追記
-
rdmd --eval
が引数をサポートするようになりました
注目トピック
言語・コンパイラ変更点
強化:ビルトインの __traits(isCopyable, T)
が追加されました
型 T
がコピー可能かどうかを確かめる __traits
が増えました。
通常の値型や参照型はコピー可能で true
、 @disable this(this);
でコピー禁止にすると false
になる、というものです。
元々 __traits(compiles, {})
を使った書き方でも実現できていて、 std.traits
にも同名の isCopyable
テンプレートがありましたが、利便性等の面からビルトインの機能として提供されるようになりました。
修正:インターフェースの実装が適切にチェックされるようになりました
インターフェースが要求するメソッドをすべて実装しているチェックが正しく動作するようになりました。
といっても無条件に起きるバグではなく、ドキュメント生成やインターフェース定義ファイル(.di)を出力するときによく使う -o-
スイッチ(オブジェクトファイルを生成しなくする)を付けるとチェックがスキップされてしまうという問題です。
元々コンパイルフェーズの後半に行われており、コンパイルの処理順序を見直して前半に移動することで修正された格好です。
もし -o-
スイッチを使っていることがあればエラーにならないか一度チェックしておくと良いかと思います。
interface Foo
{
void foo();
}
class Bar : Foo
{
// void foo() { }
}
改善:shared
変数の初期化が許可されるようになりました
別に shared
変数普通に初期化できるじゃん…?と思っていたのですが、
実際は -preview=nosharedaccess
を使っているときのロジック改善でした。
(ChangeLogにちゃんと書いてありました)
このスイッチは、今年1月に採択された DIP1024 で定義される「shared
変数への直接アクセスを禁止する」という変更を先取りするためのものです。
(core.atomic
の atomicLoad
や atomicStore
、 atomicOp
等を使うことを強制する)
たとえばフラグを付けると以下のようなコードでコンパイルエラーが発生しましたが、初期化の時点でなんでやねん、という話なので許可されます、という話です。
void main()
{
shared int n;
shared int m = 3;
}
onlineapp.d(3): Error: direct access to shared n is not allowed, see core.atomic
onlineapp.d(4): Error: direct access to shared m is not allowed, see core.atomic
強化:templateの統計を収集、一覧化するための -vtemplates
スイッチが追加されました
テンプレートの特殊化が何種類、計何回行われたか、を示す統計情報が出力できる -vtemplates
フラグが追加されました。
-vtls
など、 -v
で始まる系のフラグは情報表示が増えるシリーズなので覚えておくと良さそうです。
試しに import std.stdio;
するだけのコードで出してみると以下のようになります。
import std.stdio;
void main() { }
Number Unique Name
1 1 isFunctionPointer(T...) if (T.length == 1)
2 1 at(R)(R[] r, size_t i)
1 1 isDynamicArray(T)
1 1 Impl(T)
1 1 isDelegate(T...) if (T.length == 1)
1 1 atomicFetchSub(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, size_t mod) if (__traits(isIntegral, T) || is(T == U*, U))
1 1 IntOrLong(T)
3 1 isAggregateType(T)
3 1 TailShared(U) if (!is(U == shared))
1 1 needsLoadBarrier(MemoryOrder ms)
1 1 atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) if (!is(T == shared(U), U) && !is(T == shared(inout(U)), U) && !is(T == shared(const(U)), U))
13 1 hasUnsharedIndirections(T)
7 2 atomicValueIsProperlyAligned(T)(ref T val)
1 1 atomicFetchAdd(MemoryOrder ms = MemoryOrder.seq, T)(ref T val, size_t mod) if ((__traits(isIntegral, T) || is(T == U*, U)) && !is(T == shared))
2 1 atomicFetchAdd(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, size_t mod) if (__traits(isIntegral, T) || is(T == U*, U))
1 1 atomicFetchSub(MemoryOrder order = MemoryOrder.seq, bool result = true, T)(T* dest, T value) if (is(T : ulong))
2 1 atomicFetchAdd(MemoryOrder order = MemoryOrder.seq, bool result = true, T)(T* dest, T value) if (is(T : ulong))
71 1 __equals(T1, T2)(T1[] lhs, T2[] rhs)
1 1 atomicLoad(MemoryOrder order = MemoryOrder.seq, T)(inout(T)* src) if (CanCAS!T)
3 2 RegIndex(T)
2 2 atomicPtrIsProperlyAligned(T)(T* ptr)
1 1 classInstanceAlignment(T) if (is(T == class))
3 3 SizedReg(int reg, T = size_t)
1 1 TailShared(S) if (is(S == shared))
3 2 Unqual(T)
1 1 OriginalType(T)
9 3 atomicOp(string op, T, V1)(ref shared T val, V1 mod) if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod")))
3 3 makeGlobal(StdFileHandle _iob)()
6 4 Flag(string name)
1 1 ModifyTypePreservingTQ(alias Modifier, T)
3 3 maxAlignment(U...)
1 1 CanCAS(T)
1 1 isPointer(T)
6 1 atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(ref const shared T val) if (!hasUnsharedIndirections!T)
2 1 Demangle(T)
1 1 atomicFetchSub(MemoryOrder ms = MemoryOrder.seq, T)(ref T val, size_t mod) if ((__traits(isIntegral, T) || is(T == U*, U)) && !is(T == shared))
1 1 DynamicArrayTypeOf(T)
本気で分析するならもう少し細かい情報が欲しい気もしますが、元々 std.meta
の Filter
等をunrollして高速化する話があり、そこで得られた知見のフィードバックといったところでしょうか。
単純に特殊化の回数を減らす目的としては十分な情報だったようです。
コンパイル時間の改善にもつながりますし、メタメタしたプログラムを書く方には非常におすすめの機能なのでぜひ1度試してみてください。
思わぬテンプレートが見つかるなどの発見があるかもしれません。
改善: extern(C++)
が末尾のコンマを許容するようになりました
※2020/07/12 追記
以下のような記述が許容されるようになります。
メタプログラミングや自動生成するときに少しさぼれますし、たくさんあるときの改行や追加時の差分が見やすいので良いですね。
extern(C++, "foo",)
※こういう文法周りの改善は地味でもシンタックスハイライトが追い付かなかったりすると大変そうですね、、
改善: package(x.y)
形式の保護属性が宣言と同じモジュールを許容するようになりました
※2020/07/12 追記
package
保護属性というのは、同一パッケージ内、または他の指定したモジュールからだけ参照させる指定ができるアクセス保護属性です。
この改善は、同一パッケージ内でだけ参照できる、つまり private
と同様の働きを package
保護属性でも実現できるようになります。
メタプログラミングとかの都合でしょうか、あまり使う機会はないかもしれませんが、自由度が上がったので良いことだと思います。
module somelib;
package(somelib) void foo() { }
ランタイム変更点
強化: core.sys.linux.unistd
に exit_group
が追加されました
見出しの通りです。
Linux 2.5.35以降で使える、現在のプロセス内の全スレッドを終了させるシステムコールです。
強化: 新たなモジュールとして core.sys.darwin.mach.nlist
と core.sys.darwin.mach.stab
が追加されました
MacOSX向けで、それぞれC/C++向けヘッダーの mach-o/nlist.h
と mach-o/stab.h
に相当します。
MacOSX 10.15 (Catalina) のSDKで追加されたものということで、2019年10月リリースの2020年6月時点では最新のSDKまで対応が進んできた、ということになります。
強化: メモリ関連のGCオプションがより高精度で指定できるようになりました
メモリの少ないデバイス向けの強化で、各種メモリのサイズ指定に B
, K
, M
, G
といった接尾語を付けることで細かい指定ができるようになりました。
GCオプションはプログラム中に埋め込む方法と実行時に与える方法がありますが、それぞれ以下のようになります。
extern(C) __gshared string[] rt_options =
[ "gcopt=minPoolSize:4K maxPoolSize:2M incPoolSize:8K" ];
app "--DRT-gcopt=minPoolSize:4K"
今まで通り数字だけ指定した場合はメガバイト単位での指定になります。
強化: ReadWriteMutex
の tryLock
にタイムアウトを指定できるようになりました
※2020/07/12 追記
ReadWriteMutex
は、データの読み取りを管理する Reader
と書き込みを管理する Writer
が協調するロック機構です。
読み取りと書き込みを分けることでデータ競合が起きないことを保証でき、排他される機会を減らし効率化できます。
詳細はこちら。
-
core.sync.rwmutex
今回追加されたのは、lock
が取得できるか試すために使う tryLock
で、これに引数で時間間隔を表す Duration
が指定できるようになりました。
auto rwmutex = new ReadWriteMutex;
const locked = rwmutex.writer.tryLock(1.seconds); // 1秒以内にロックがほしい
if (locked) {
scope (exit) rwmutex.writer.unlock();
// データの書き込み
}
tryLock
で一定時間待つような実装をしている場合、これで置き換えることができます。
記述が簡単になるのはありがたいですね。
ライブラリ変更点
改善: Duration
に対する std.algorithm.sum
の引数なし呼び出しができるようになりました
※2020/07/12 追記
元々、以下のようなコードがエラーになっていました。
auto s = [1.minutes, 1.minutes].sum();
assert(s == 2.minutes);
これは Duration
がやや特殊な構造体で、単一の数値を表す構造体にもかかわらず Duration(0)
という形式でゼロに対応する値が作れない、ということに起因しています。
(Duration
の 0
は Duration.zero
で取得します)
これが今回から .zero
のプロパティを考慮するようになり、上記コードも動くようになります。
強化: chunkBy
が ref
引数による述語をサポートします
※2020/07/12 追記
chunkBy
というのは配列などをブロック毎に区切るための関数です。
auto s = [1, 1, 1, 2, 2, 2, 3, 3, 3];
writeln(s.chunkBy!((e1, e2) => e1 == e2));
[[1, 1, 1], [2, 2, 2], [3, 3, 3]]
今回から以下のように ref
がつけられるようになります。これで効率化されるシーンがあるかもしれません。
auto s = [1, 1, 1, 2, 2, 2, 3, 3, 3];
writeln(s.chunkBy!((ref e1, ref e2) => e1 == e2));
改善: std.algorithm.mutation
の move
が onPostMove
の属性を考慮するようになりました
※2020/07/12 追記
あまり使うことはないかもしれませんが、move
はムーブ対象がコピー可能な場合、そのオブジェクトの onPostMove
というメソッドを実行するようになっています。
この部分が今まで @trusted
を付けた実装になっており、opPostMove
が @system
でも無視されてしまっていました。
これがコンパイラによって正しくチェックされるようになり、以下のようなコードがエラーになるようになります。
import std.algorithm.mutation;
struct S
{
void opPostMove(const ref S old) @system nothrow pure
{
assert(a == old.a);
a++;
}
int a;
}
void main () @safe nothrow
{
S s1;
s1.a = 41;
S s2 = move(s1);
assert(s2.a == 42);
}
改善: 配列でないレンジに対する std.digest.digest!(Hash, Range)
の速度が改善されました
※2020/07/12 追記
これは以下の要領で任意のRangeデータからハッシュが求められる関数です。
import std.digest.md;
import std.range : repeat;
auto testRange = repeat!ubyte(cast(ubyte)'a', 100);
auto md5 = digest!MD5(testRange);
速度改善の結果や差分は以下にまとまっています。
愚直にパターン毎の実装を切り替え、全体的に実行時間が半分以下になった、という感じです。
インストーラー変更点
強化: インストールスクリプトが Windows のコマンドプロンプトで動作するようになりました
文字通りの強化です。
インストールスクリプトというのは dub や dmd をコマンド実行でインストールする仕組みのことです。
インストールスクリプトの説明は以下のあたりが詳しいです。
- https://qiita.com/outlandkarasu@github/items/faa555d5c1d1d19a8fa4
- https://blog.kotet.jp/2017/12/d-install-script/
ただ、install.sh
というファイルはそのままで、特に install.bat
ができたわけでも install.sh
をダブルクリックして実行できるわけでもありません。
どういうことかというと、今までは POSIX のターミナルエミュレータでしか動かすことができなかったものが、MSYS2がインストールされている場合、それを使ってコマンドプロンプトから実行できるようになった、という強化です。
MSYS2
が C:\msys64
にインストールされているとして、以下のように実行できます。
C:\msys64\usr\bin\pacman.exe --sync unzip p7zip
C:\msys64\usr\bin\curl.exe https://dlang.org/install.sh | \msys64\usr\bin\bash.exe -s
C:\msys64\usr\bin\bash.exe %USERPROFILE%\dlang\install.sh --help
ガッツリbash等使っており、要するにLinux環境と同じことをやっているだけです。
ただ、わざわざターミナル起動しなくて良いので、batファイルとかにまとめれば良いのはありがたいですね。
差分はこちら。
対応されたのは Extended-Pascal から D へのトランスパイラを書いている Bastiaan Veelo という方です。
DConf 2019の登壇者の方ですね。
DUB変更点
強化: ルートパッケージのターゲットに関する環境変数が追加されました
dubの設定ファイルで使える以下の環境変数が追加されました。
いずれもルートパッケージの情報を取得できるもので、
有り体に言って、参照された側が参照している側(大元)について情報を取得できます
-
DUB_ROOT_PACKAGE_TARGET_TYPE
- ターゲット種別、実行ファイルかライブラリか等
-
DUB_ROOT_PACKAGE_TARGET_PATH
- ターゲットパス、ルートパッケージの設定ファイルのある場所
-
DUB_ROOT_PACKAGE_TARGET_NAME
- パッケージ名
ターゲット種別が分かるのは結構ありがたいですね。
単体テスト系のライブラリで動きを変えるとか色々使い道がありそうです。
強化: CLIの拡張性が改善されました
DUBをライブラリとして利用している方向けの強化です。
主にコマンドを追加しやすくするため、引数解析周りの改善が行われたというものです。
dub
のようにいくつかのサブコマンドを持っていたりすると、 --
で区切って実行時に渡す引数と分けることがあります。
これは単体テストを見ると分かりますが、引数を以下のように分類して扱うものです。
コマンド | コマンド引数 | アプリケーション引数 |
---|---|---|
app --version |
なし | --version |
app a b |
a , b
|
なし |
app a --version |
a , --version
|
なし |
dub -- a |
なし | a |
app 1 2 -- a |
1 , 2
|
a |
実装としては、 CommandArgs
クラスに hasAppArgs
と appArgs
という「アプリケーション用の引数」に関するメソッドが追加されています。
実際の差分はこちらです。
ツール変更点
強化: rdmd --eval
で引数が渡せるようになりました
※2020/07/12 追記
rdmd --eval
は、渡した文字列をコンパイルして実行するところまで一発でやってくれる補助ツールです。
rdmd
に --eval
という引数で文字列を渡すとそのまま実行してくれます。
rdmd --eval="writeln(`Hello, world!`);"
これに動的な要素を埋め込もうと思うと、ひと手間エスケープする必要があって面倒でした。
今後は --
で区切ったあとの引数が string[] args
が使えるようになり、より簡単に動的な要素が埋め込めます。
rdmd --eval="writeln(args[1 .. $]);" -- a b c
["a", "b", "c"]
ターミナルやコマンドプロンプトからちょっと呼ぶのに連携しやすくなった感じですね。
非推奨または廃止される機能
今回の更新では、あまり使われない機能の非推奨が1つだけです。非常に安定していますね。(フラグ)
非推奨
-
object.selector
が非推奨になりました
非推奨: object.selector
が非推奨になりました
これは何かというと Objective-C
連携するときに使うものです。
UDAとして付与することにより、メソッドの定義に「セレクタ」(メソッドを特定する内部表現)の情報を付け足します。
今後は core.attributes
をimportすれば同じように使うことができます。
(ただしバージョン識別子として D_ObjectiveC
を定義した場合のみ)
~~滅多に使われない割に、~~必ず import される object モジュールに定義されていました。
今回コンパイラ実装の範囲からランタイム実装のほうに移動された格好になります。
import core.attributes : selector;
extern (Objective-C)
extern class NSObject
{
static NSObject alloc() @selector("alloc");
}
extern (Objective-C)
extern class NSString
{
NSString initWith(in char*) @selector("initWithUTF8String:");
NSString initWith(NSString) @selector("initWithString:");
}
このあたりの仕様は以下を参照ください。
- Interfacing to Objective-C
まとめ
比較的小規模なバージョンアップとなっていたので全機能紹介してみました。
今回はライブラリの強化はありませんが、地味にパフォーマンス改善などがは行われているほか、5月版で実験的に追加された所有権周りも幾分賢くなっているようです。
前回のリリースからこの2ヶ月、リソースとしては言語の強化に向けたDIPのチェック等に振り向けられていた印象ですが、特に遅れることもなく無事にリリースされそうです。
ベータ版が取れたら改めて更新したいと思います。
次回リリースは9月1日予定、引き続きの強化を期待したいと思います!