slidenumbers: true autoscale: true
- コンパイラの「ドライバ」について知る
swift-driverの詳細について知る- 実際に コード読ませながら進行します。
- わかんなくなったら遠慮せず、適宜止めてください
- うまく説明できないのでとりあえずググったらなんかいい回答出てきた
- What is a driver, for example, a compiler driver? at Quora
- Ken Gregg (Founder and CEO, Bytellect LLC) さんが説明
- There are several uses of the term “driver” in the world of computer software and hardware.
- コンピュータの世界やと、ハードウェアとソフトウェア(開発)の世界で違うんやで
- The most common is device driver (sometimes just shortened to driver), which is a piece of software that allows an operating system to communicate with a hardware device.
- 大体の場合「ドライバ」というとハードウェアの世界を指してて、OSとデバイスのやりとりをサポート(許可)するやつやで
- バスドライバ・フィルタドライバ・ソフトウェアドライバ(ソフトウェアの世界の説明ではない)...
- In software development in general, the term driver is sometimes used to refer to a piece of software that controls or drives some other piece of software.
- ソフトウェア開発の世界やと一般的には、ドライバという言葉はいくつかのパーツになっているソフトウェアを参照したり、呼び出したりするソフトウェアのことを指すやで
- For example, the LLVM Compiler Driver (llvmc) is a tool that can be configured to invoke several compiler development tools and related tools, acting as a single point of access for users of the LLVM set of tools.
- たとえば、LLVM Compiler Driver はいくつかのコンパイラ(とその関連)ツールの呼び出しを構成するものやで
- ユーザから見たらLLVMのツール群にアクセスできる、一つのアクセスポイントとして振る舞うやで
- The llvmc tool is a configurable compiler driver. As such, it isn't a compiler, optimizer, or a linker itself but it drives (invokes) other software that perform those tasks. If you are familiar with the GNU Compiler Collection's gcc tool, llvmc is very similar.
llvmc自体は コンパイラ・Optimizer・リンカ自身 じゃないでllvmcはドライバとして コンパイラ・Optimizer・リンカ自身 を動かす(drives・呼び出す)やで
- みなさんがコンパイラだと思っている
gccは、 実は、コンパイラドライバ(compiler driver)と呼ばれるプログラムであり、 Cなどのプログラミング言語で書かれたソースプログラムから実行形式を 作り出すための処理を行う。 gccは、「必要に応じてコンパイラや アセンブラ、リンケージエディタなどのプログラムを呼び出す」という 処理を行っている。gccコマンド自体が直接コンパイルを行っているわけではない。- 「コンパイル」と呼ばれている処理は
cc1という別のコマンドがやっている
- 「コンパイル」と呼ばれている処理は
- ドライバは、コンパイラ及びその関連ツール(Optimizer・リンカなど)を呼び出すツール
gccやllvmc、swiftと言ったコマンドは、ドライバであって、コンパイラ自身ではない
swift-frontendコマンドからシンボリックリンクでswiftとswiftcを作っている- のちのち、とある分岐でこのシンボリックリンクの名前がドライバとしての挙動の分岐になるが後述
- ちなみに、ドライバが呼び出す仕事の単位(リンクなど)をジョブ と呼ばれる
- main/lib/Driver にドライバのコードが有る
- 今回は C++ で書かれた本体の Driver ではなく、Swiftで書かれた
swift-driverを見る
- Swift ドライバの Swift による書き換え
- Appleのエンジニアによる、SNSでの初出の言及
- メリットはこの Twitter スレで詳しく説明されている
- アホみたいな質問かもしれへんけど、これ使う良い点何?
- アホみたいな質問じゃないで。一つの大きな利点は、このドライバはSwiftPMの外側のビルドシステムからは不透明な、インクリメンタルビルドのための内部ビルドシステムを備えているやで
- leading to oversubscribing the machine when (say) building lots of targets in parallel. がよくわからん
- このドライバ使うと、SwiftPM はドライバのビルドタスクを自身のビルドグラフに統合できるし、一つのシングルキューで並行タスクを管理できるやで。
- これで、マシンのコアを最低限の競合で有効活用できるやで。
※ 補足: 競合 … CPUのリソースをタスクが取り合うことを指す
- 他だとアーキテクチャの面でもメリットがあるんや
- このドライバで動的スケジューリングができるんやで、これで、ワイたちが色々やってきた改善の実装がやりやすくなるんや。
- あと個人的にSwiftで書けるのが嬉しいやで
- 実際に環境構築をして動かしてみよう
- Xcode 12 (試した環境)
swift-driverのコード- なるべく新しめの Swift Trunk ToolChain(SnapShot)
- Clone する
$ git clone https://github.com/apple/swift-driver
$ cd swift-driverswift buildでビルドする
# swift-driver にて
$ swift build- ビルドされた
swift-driverをswiftswiftcという名前でシンボリックリンクを貼る
$ ln -s .build/debug/swift-driver swift
$ ln -s .build/debug/swift-driver swiftc- 試しに
swiftを動かす
$ ./swift
error: failed to retrieve frontend target info- 動かない
- 動かすには swift-frontend の情報が必要
$ /Library/Developer/Toolchains/{先程インストールしたToolChainのパス}/usr/bin/swift-frontend -frontend -print-target-info
{
"compilerVersion": "Apple Swift version 5.3-dev (LLVM 2685951d827b16e, Swift ec72db1d8ab63fa)",
"target": {
"triple": "x86_64-apple-macosx10.15",
"unversionedTriple": "x86_64-apple-macosx",
"moduleTriple": "x86_64-apple-macos",
"swiftRuntimeCompatibilityVersion": "5.1",
"compatibilityLibraries": [
{
"libraryName": "swiftCompatibility51",
"filter": "all"
}
],
"librariesRequireRPath": false
},
"paths": {
"runtimeLibraryPaths": [
"/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2020-09-22-a.xctoolchain/usr/lib/swift/macosx",
"/usr/lib/swift"
],
"runtimeLibraryImportPaths": [
"/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2020-09-22-a.xctoolchain/usr/lib/swift/macosx"
],
"runtimeResourcePath": "/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2020-09-22-a.xctoolchain/usr/lib/swift"
}
}- Xcode 12 同封の Swift だと、
swift -frontend -print-target-infoで同様のものが出てくる - ただし、こちらは
swift-driverでは動かない
- 動かすには
swift-frontendの情報が必要 - -> 環境変数
SWIFT_DRIVER_SWIFT_FRONTEND_EXECで先程のswift-frontendを指定して run すれば良い
$ export SWIFT_DRIVER_SWIFT_FRONTEND_EXEC=/Library/Developer/Toolchains/{ToolChainのパス}/usr/bin/swift-frontend
$ ./swift
Type :help for assistance.
1>- これでREPLが起動する
swiftcも動く
$ ./swift test.swift
- Package.swift を直接開いて作業しても、なぜか Break Point が
main.swiftだけ効かない - 僕の環境だけかも知れない・・・・
- これでも無理だった。他の関数では行ける
swift package generate-xcodeprojで xcodeproj 上で作業 + 上記設定だとうまく効く
- 出力されたもののうち、
pathsの情報にフォーカスしてみる
{
"runtimeLibraryPaths": [
"/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2020-09-22-a.xctoolchain/usr/lib/swift/macosx",
"/usr/lib/swift"
],
"runtimeLibraryImportPaths": [
"/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2020-09-22-a.xctoolchain/usr/lib/swift/macosx"
],
"runtimeResourcePath": "/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2020-09-22-a.xctoolchain/usr/lib/swift"
}runtimeLibraryImportPaths- "/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2020-09-22-a.xctoolchain/usr/lib/swift/macosx"
- CoreGraphics とか Foundation とかの
dylibがある
- 要するに、CoreGraphics とか Foundation とかの
dylibのパスを集めたりしている- リンカとかのジョブを生成するときに、適宜引き渡す
- LinkJob.swift のときに説明
- Sources/SwiftDriver/Jobs で定義されている
- 今日見るのは ...
- Job.swift
- LinkJob.swift
- Planning.swift
- どういうジョブかを表す
Job構造体が定義されている
- ライブラリのリンクのジョブ を返す関数
- ***Job.swift というファイルはだいたいそのジョブを返す関数の実装
- ドライバ (
Driver型) の状態から、適切なジョブ群を返すplanBuild()の実装があるDriverのextension
- ***Job.swift の中の関数を適宜呼び出してJobを作ったり
planBuild()関数がDriverの状態から適切なジョブ群を返すJob方は、ジョブを表す構造体が定義されている- ***Job.swift というファイルはだいたいそのジョブを返す関数の実装
planBuild() 関数が呼ばれるのは?
- テストを除くと
main.swiftのみから呼び出されている
- ある程度読む材料が整ったので、
main.swiftを覗く swift-driverの プログラムの始まり- ここをざっと読むと、ドライバの役割が何となく分かる
import SwiftDriverExecution
import SwiftDriver
import TSCLibc
import TSCBasic
import TSCUtility
var intHandler: InterruptHandler?
let diagnosticsEngine = DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler])
do {
let processSet = ProcessSet()
intHandler = try InterruptHandler {
processSet.terminate()
}
let (mode, arguments) = try Driver.invocationRunMode(forArgs: CommandLine.arguments)
if case .subcommand(let subcommand) = mode {
// We are running as a subcommand, try to find the subcommand adjacent to the executable we are running as.
// If we didn't find the tool there, let the OS search for it.
let subcommandPath = Process.findExecutable(arguments[0])?.parentDirectory.appending(component: subcommand)
?? Process.findExecutable(subcommand)
if subcommandPath == nil || !localFileSystem.exists(subcommandPath!) {
fatalError("cannot find subcommand executable '\(subcommand)'")
}
// Execute the subcommand.
try exec(path: subcommandPath?.pathString ?? "", args: arguments)
}
let executor = try SwiftDriverExecutor(diagnosticsEngine: diagnosticsEngine,
processSet: processSet,
fileSystem: localFileSystem,
env: ProcessEnv.vars)
var driver = try Driver(args: arguments,
diagnosticsEngine: diagnosticsEngine,
executor: executor)
// FIXME: The following check should be at the end of Driver.init, but current
// usage of the DiagnosticVerifier in tests makes this difficult.
guard !driver.diagnosticEngine.hasErrors else { throw Diagnostics.fatalError }
let jobs = try driver.planBuild()
try driver.run(jobs: jobs)
if driver.diagnosticEngine.hasErrors {
exit(EXIT_FAILURE)
}
} catch Diagnostics.fatalError {
exit(EXIT_FAILURE)
} catch let diagnosticData as DiagnosticData {
diagnosticsEngine.emit(.error(diagnosticData))
exit(EXIT_FAILURE)
} catch {
print("error: \(error)")
exit(EXIT_FAILURE)
}- 比較的短い!
- 上から読んでいく
- まずははじめから
var intHandler: InterruptHandler?
let diagnosticsEngine = DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler])
do {
let processSet = ProcessSet()
intHandler = try InterruptHandler {
processSet.terminate()
}
...var intHandler: InterruptHandler?
let diagnosticsEngine = DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler]) // これ
do {
let processSet = ProcessSet()
intHandler = try InterruptHandler {
processSet.terminate()
}
...DiagnosticsEngine- 見た感じ、error や warning といった診断の結果をハンドリングする
main.swiftだとDriver.stderrDiagnosticsHandlerを渡しているswift-driverのコードではなく、apple/swift-tools-support-core のコード
public static let stderrDiagnosticsHandler: DiagnosticsEngine.DiagnosticsHandler = { diagnostic in
let stream = stderrStream
if !(diagnostic.location is UnknownLocation) {
stream <<< diagnostic.location.description <<< ": "
}
switch diagnostic.message.behavior {
case .error:
stream <<< "error: "
case .warning:
stream <<< "warning: "
...
- error とか warning を 標準エラー出力として出すようにハンドリングしている
var intHandler: InterruptHandler? // これ
let diagnosticsEngine = DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler])
do {
let processSet = ProcessSet()
intHandler = try InterruptHandler { // これ
processSet.terminate()
}
...InterruptHandler- 割り込みシグナル (
Ctrl + C) 等のシグナルが走ったときのハンドラ main.swiftではProcessSetを terminate するようにハンドルしているswift-driverのコードではなく、apple/swift-tools-support-core のコード
- 割り込みシグナル (
var intHandler: InterruptHandler?
let diagnosticsEngine = DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler])
do {
let processSet = ProcessSet() // これ
intHandler = try InterruptHandler {
processSet.terminate() // これ
}
...ProcessSet- 簡単に言えばプロセスのコレクション
Jobは最終的にこのコレクションにプロセスとして登録されるswift-driverのコードではなく、apple/swift-tools-support-core のコード
- ジョブのプロセスとしてのハンドリング
- error、warning 等々 をエラー標準出力としてハンドリング
- 上記の前処理のようなことをやっている
- 次はここから読む
...
let (mode, arguments) = try Driver.invocationRunMode(forArgs: CommandLine.arguments)
if case .subcommand(let subcommand) = mode {
// We are running as a subcommand, try to find the subcommand adjacent to the executable we are running as.
// If we didn't find the tool there, let the OS search for it.
let subcommandPath = Process.findExecutable(arguments[0])?.parentDirectory.appending(component: subcommand)
?? Process.findExecutable(subcommand)
if subcommandPath == nil || !localFileSystem.exists(subcommandPath!) {
fatalError("cannot find subcommand executable '\(subcommand)'")
}
// Execute the subcommand.
try exec(path: subcommandPath?.pathString ?? "", args: arguments)
}
......
let (mode, arguments) = try Driver.invocationRunMode(forArgs: CommandLine.arguments) // ここ
if case .subcommand(let subcommand) = mode {
// We are running as a subcommand, try to find the subcommand adjacent to the executable we are running as.
// If we didn't find the tool there, let the OS search for it.
let subcommandPath = Process.findExecutable(arguments[0])?.parentDirectory.appending(component: subcommand)
?? Process.findExecutable(subcommand)
if subcommandPath == nil || !localFileSystem.exists(subcommandPath!) {
fatalError("cannot find subcommand executable '\(subcommand)'")
}
// Execute the subcommand.
try exec(path: subcommandPath?.pathString ?? "", args: arguments)
}
...swiftかswiftcで呼び出しているのか、どんなオプションが付いているか、などで Driver のモードを変化- 例えば、ここで REPL かどうかも判定している
- サブコマンドを見るモードを返すこともある
- 例)
swift buildとかはサブコマンドを見る対象になる
...
let (mode, arguments) = try Driver.invocationRunMode(forArgs: CommandLine.arguments)
if case .subcommand(let subcommand) = mode { // ここ
// We are running as a subcommand, try to find the subcommand adjacent to the executable we are running as.
// If we didn't find the tool there, let the OS search for it.
let subcommandPath = Process.findExecutable(arguments[0])?.parentDirectory.appending(component: subcommand)
?? Process.findExecutable(subcommand)
if subcommandPath == nil || !localFileSystem.exists(subcommandPath!) {
fatalError("cannot find subcommand executable '\(subcommand)'")
}
// Execute the subcommand.
try exec(path: subcommandPath?.pathString ?? "", args: arguments)
}
...-
サブコマンドを探索する。
-
見つからなければ error
fatalError("cannot find subcommand ...
-
見つかったらサブコマンドを呼び出す
try exec(path:, args:)
swift-driverがどのように呼ばれるか調べるswiftorswiftcorswift+ サブコマンド
- サブコマンドがあれば、そのサブコマンドを探して呼び出す
...
let executor = try SwiftDriverExecutor(diagnosticsEngine: diagnosticsEngine,
processSet: processSet,
fileSystem: localFileSystem,
env: ProcessEnv.vars)
var driver = try Driver(args: arguments,
diagnosticsEngine: diagnosticsEngine,
executor: executor)
// FIXME: The following check should be at the end of Driver.init, but current
// usage of the DiagnosticVerifier in tests makes this difficult.
guard !driver.diagnosticEngine.hasErrors else { throw Diagnostics.fatalError }
let jobs = try driver.planBuild()
try driver.run(jobs: jobs)
......
// ここ
let executor = try SwiftDriverExecutor(diagnosticsEngine: diagnosticsEngine,
processSet: processSet,
fileSystem: localFileSystem,
env: ProcessEnv.vars)
var driver = try Driver(args: arguments,
diagnosticsEngine: diagnosticsEngine,
executor: executor)
// FIXME: The following check should be at the end of Driver.init, but current
// usage of the DiagnosticVerifier in tests makes this difficult.
guard !driver.diagnosticEngine.hasErrors else { throw Diagnostics.fatalError }
let jobs = try driver.planBuild()
try driver.run(jobs: jobs)
...DriverExecutorプロトコルに準拠してる- ジョブを動かすことがメインの働き
- さっきの
ProcessSetとかDiagnosticsEngine、環境変数のデータなどを渡している- ジョブをプロセスにして
ProcessSetに渡したり、error をDiagnosticsEngineで ハンドリングしたりしているっぽい
- ジョブをプロセスにして
...
let executor = try SwiftDriverExecutor(diagnosticsEngine: diagnosticsEngine,
processSet: processSet,
fileSystem: localFileSystem,
env: ProcessEnv.vars)
// ここ
var driver = try Driver(args: arguments,
diagnosticsEngine: diagnosticsEngine,
executor: executor)
// FIXME: The following check should be at the end of Driver.init, but current
// usage of the DiagnosticVerifier in tests makes this difficult.
guard !driver.diagnosticEngine.hasErrors else { throw Diagnostics.fatalError }
let jobs = try driver.planBuild()
try driver.run(jobs: jobs)
...- 言わずもがなドライバ本体
- イニシャライザで、ジョブ実行に必要な
ProcessSetや error ハンドリングのためのDiagnosticsEngineを渡す - ドライバに渡されたオプションも渡す (
arguments)
...
let executor = try SwiftDriverExecutor(diagnosticsEngine: diagnosticsEngine,
processSet: processSet,
fileSystem: localFileSystem,
env: ProcessEnv.vars)
var driver = try Driver(args: arguments,
diagnosticsEngine: diagnosticsEngine,
executor: executor)
// FIXME: The following check should be at the end of Driver.init, but current
// usage of the DiagnosticVerifier in tests makes this difficult.
guard !driver.diagnosticEngine.hasErrors else { throw Diagnostics.fatalError }
// ここ
let jobs = try driver.planBuild()
try driver.run(jobs: jobs)
...- 渡されたオプション(イニシャライザで渡した
arguments)をもとに、さっき説明したplanBuild()でジョブ群を生成する
...
let executor = try SwiftDriverExecutor(diagnosticsEngine: diagnosticsEngine,
processSet: processSet,
fileSystem: localFileSystem,
env: ProcessEnv.vars)
var driver = try Driver(args: arguments,
diagnosticsEngine: diagnosticsEngine,
executor: executor)
// FIXME: The following check should be at the end of Driver.init, but current
// usage of the DiagnosticVerifier in tests makes this difficult.
guard !driver.diagnosticEngine.hasErrors else { throw Diagnostics.fatalError }
let jobs = try driver.planBuild()
// ここ
try driver.run(jobs: jobs)
...- 生成されたジョブを実行する
- Swift のドライバはオプションでどういうジョブを実行するかを決める
- ジョブはプロセスとして動かされる
if driver.diagnosticEngine.hasErrors {
exit(EXIT_FAILURE)
}
} catch Diagnostics.fatalError {
exit(EXIT_FAILURE)
} catch let diagnosticData as DiagnosticData {
diagnosticsEngine.emit(.error(diagnosticData))
exit(EXIT_FAILURE)
} catch {
print("error: \(error)")
exit(EXIT_FAILURE)
}- 後は
try catchのエラーハンドリングだけです - 説明しなくてもだいたいわかると思います
- とりあえず好評だったら Vol.2 やります
- @kateinoigakukun san
- ちなみに彼はすでにContributeしています
- What is a driver, for example, a compiler driver? - Quora
- Introduction - The LLVM Compiler Driver (llvmc)
- コンパイラは何をしているのか - 大瀧 研究室 茨城大学
- apple/swift-driver
- Harlan Haskins - Twitter
- Javi - Twitter










