コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2021年7月3日 >>> 2021年6月20日
link もっと後

2021年7月3日

OpenCLのOSS実装poclを調べる その8 - poclとClang/LLVMの引数

目次: OpenCL

前回に続いてpoclとClang/LLVMの関係について説明します。Clangは引数の体系が2つあります。1つは通常使用するGCCと互換があるオプション、もう1つはclang -cc1オプションを指定したときに使う内部用オプションです。正式な名前がわからないので適当に呼んでいます。正式な名前をご存知の方は教えていただけると嬉しいです。

前者と後者は -Iや -oのように同じ名前の場合もありますし、顕著に異なる場合もあります。RISC-V向けのmarchやmabiは差が顕著なので、例として紹介します。

GCC互換オプション
$ riscv32-unknown-elf-gcc b.c -c -march=rv32gc -mabi=ilp32f

$ clang --target=riscv32 b.c -c -march=rv32gc -mabi=ilp32f

GCCとClangのオプションがほぼ同じですね。次にClang内部用のオプションを確認します。内部用のオプションは -vを指定すると表示できます。

Clang内部用オプションを表示
$ clang --target=riscv32 b.c -c -march=rv32gc -mabi=ilp32f -v

Debian clang version 11.0.1-2
Target: riscv32--
Thread model: posix
InstalledDir: /usr/bin
 (in-process)
 "/usr/lib/llvm-11/bin/clang" -cc1 -triple riscv32-- -emit-obj -mrelax-all -disable-free -disable-llvm-verifier -discard-value-names -main-file-name b.c -mrelocation-model static -mframe-pointer=all -fmath-errno -fno-rounding-math -mconstructor-aliases -nostdsysteminc -target-feature +m -target-feature +a -target-feature +f -target-feature +d -target-feature +c -target-feature +relax -target-feature -save-restore -target-abi ilp32f -msmall-data-limit 8 -fno-split-dwarf-inlining -debugger-tuning=gdb -v -resource-dir /usr/lib/llvm-11/lib/clang/11.0.1 -internal-isystem include -fdebug-compilation-dir /home/katsuhiro/share/projects/c/clang_test -ferror-limit 19 -fno-signed-char -fgnuc-version=4.2.1 -fcolor-diagnostics -faddrsig -o b.o -x c b.c
clang -cc1 version 11.0.1 based upon LLVM 11.0.1 default target x86_64-pc-linux-gnu
ignoring nonexistent directory "include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/llvm-11/lib/clang/11.0.1/include
End of search list.

先ほどと全く違うことがわかると思います。このままだと色々ごちゃごちゃ表示されていてわかりにくいので、対応するオプションだけ抜粋します。

Clang内部用オプション(対応部分のみ抜粋)
--target=riscv32: -triple riscv32--
-march=rv32gc   : -target-feature +m -target-feature +a -target-feature +f -target-feature +d -target-feature +c
-mabi=ilp32f    : -target-abi ilp32f

なお -target-featureはカンマ区切りで複数指定することもできるようです。上記で言えば -target-feature +m,+a,+f,+d,+cにしても良いです。

poclと何の関係が?

なぜこの話をしたのかというとpocl内でLLVMを呼び出す際は、Clang内部用オプションを使わなければならない箇所があるからです。一番わかりやすい(文字列の形でオプションを指定している)のはpocl_llvm_build_program() です。

poclがClang内部用オプションを使っている箇所(文字列で設定するタイプ)

// pocl/lib/CL/pocl_llvm_build.cc

int pocl_llvm_build_program(cl_program program,
                            unsigned device_i,
                            const char *user_options_cstr,
                            char *program_bc_path,
                            cl_uint num_input_headers,
                            const cl_program *input_headers,
                            const char **header_include_names,
                            int linking_program)

{

...

  CompilerInvocation &pocl_build = CI.getInvocation();

...

  // This is required otherwise the initialization fails with
  // unknown triple ''
  ss << "-triple=" << device->llvm_target_triplet << " ";    //★これ
  if (device->llvm_cpu != NULL)
    ss << "-target-cpu " << device->llvm_cpu << " ";    //★これも同様

...

  std::istream_iterator<std::string> begin(ss);
  std::istream_iterator<std::string> end;
  std::istream_iterator<std::string> i = begin;
  std::vector<const char*> itemcstrs;
  std::vector<std::string> itemstrs;
  while (i != end) {
    itemstrs.push_back(*i);    //★std::vectorにオプションのstd::stringを並べる
    ++i;
  }

  for (unsigned idx = 0; idx < itemstrs.size(); idx++) {
    // note: if itemstrs is modified after this, itemcstrs will be full
    // of invalid pointers! Could make copies, but would have to clean up then...
    itemcstrs.push_back(itemstrs[idx].c_str());    //★std::vectorにオプションの文字列のポインタを並べる
  }

...

  //★コンパイラに上記のオプションを指定する(この時点ではまだコンパイラは起動しない)
  if (!CompilerInvocation::CreateFromArgs(
          pocl_build,    //★CompilerInvocation
#ifndef LLVM_OLDER_THAN_10_0
          ArrayRef<const char *>(itemcstrs.data(),
                                 itemcstrs.data() + itemcstrs.size()),
#else
          itemcstrs.data(), itemcstrs.data() + itemcstrs.size(),
#endif
          diags)) {

あとはpocl_llvm_codegen() も同様ですが、指定の方法が違います。-target-feature +mのような文字列ではなく、TargetMachineのメソッドを呼び出して指定します。コードで見たほうがわかりやすいでしょう。

poclがClang内部用オプションを使っている箇所(関数で設定するタイプ)

// pocl/lib/CL/pocl_llvm_wg.cc

int pocl_llvm_codegen(cl_device_id Device, void *Modp, char **Output,
                      uint64_t *OutputSize) {

...

  llvm::Triple Triple(Device->llvm_target_triplet);
  llvm::TargetMachine *Target = GetTargetMachine(Device, Triple);

  // First try direct object code generation from LLVM, if supported by the
  // LLVM backend for the target.
  bool LLVMGeneratesObjectFiles = true;

  SmallVector<char, 4096> Data;
  llvm::raw_svector_ostream SOS(Data);
  bool cannotEmitFile;

  cannotEmitFile = Target->addPassesToEmitFile(PMObj, SOS,
#ifndef LLVM_OLDER_THAN_7_0
                                  nullptr,
#endif
                                  CODEGEN_FILE_TYPE_NS::CGFT_ObjectFile);

  Target->setTargetFeatureString("+m,+a,+f");    //★-target-feature +m,+a,+fに相当する

独自アクセラレータ向けの実装を行う際にClang/LLVMのオプションを変えたくなることは多々ありますから、今回のオプションの違いは今後の説明でも登場するはずです。たぶん。

編集者:すずき(2023/09/24 11:58)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2021年7月2日

OpenCLのOSS実装poclを調べる その7 - poclとLLVMとOpenCLカーネルビルド

目次: OpenCL

今回は少し話題を変えてpoclとClang/LLVMの関係について説明します。poclはOpenCL C言語をビルドするため内部的にClang/LLVMを呼び出します。ビルドはおおまかに2段階に分かれています。設定次第で多少変わりますが、ざっくり各段階で何をしているのか説明します。まだ話題にしていない関数も出てきますが、その話は追々やっていこうと思います。

第1段階OpenCL C -> LLVM IR

最初はclBuildProgram() です。やっていることはOpenCL C -> LLVM IR Bitcode(コンパイル+OpenCLランタイムとのリンク)です。処理に関連する関数と呼び出し関係は下記のとおりです。

clBuildProgram() とLLVM IR Bitcode生成関数の呼び出し関係
clBuildProgram()
  compile_and_link_program()
    pocl_llvm_build_program(): プリプロセス、コンパイル(出力LLVM IR Bitcode)
      link(): OpenCLランタイムとリンク(未定義シンボルの検出)
      pocl_write_module(): 出力LLVM IR Bitcode

この関数の実行中に3つ一時ファイルが出力されます。本来は消されてしまって残らないファイルもありますが、コードを変更し一時ファイルをあえて残すと、

  • tempfile-xx-xx-xx-xx-xx.cl: オリジナルのOpenCL Cコード(xx-xx-... の部分はハッシュ値)
  • tempfile-xx-xx-xx-xx-xx.preproc.cl: プリプロセス後のOpenCL Cコード(xx-xx-... の部分はハッシュ値)
  • ~/.cache/pocl/kcache/XX/XXXXXXXX/program.bc: リンク後のLLVM IR Bitcode(XXはハッシュ値なので名前は様々)

が作成されます。link() は全てのシンボルを問答無用で追加するのではなくて、OpenCLカーネルコードから参照されているシンボル(=未定義のシンボル)のみを追加します。

第2段階LLVM IR -> バイナリ

次はclEnqueueNDRangeKernel() です。やることはLLVM IR Bitcode -> ターゲットバイナリです。

API名はコンパイルやビルドと関係なさそうに見えますが、poclのホストCPU向け実装を見る限り、NDRangeKernelから起因してLLVM IR Bitcodeからターゲットデバイスのバイナリに変換しています(あと何度も変換しなくて良いように生成したバイナリをキャッシュする)。

処理に関連する関数と呼び出し関係は下記のとおりです。

ホストCPU向け実装の呼び出し関係
clEnqueueNDRangeKernel()
  pocl_command_enqueue()
    pocl_pthread_submit()
      pthread_scheduler_push_command(): キューにコマンドを追加する、ワーカースレッドが起床してコマンドを得る

start_thread(): ワーカースレッド
  pocl_pthread_driver_thread()
    pthread_scheduler_get_work()
    check_cmd_queue_for_device(): キューからコマンドを得る、コマンドがNDRangeKernelの実行要求だったら、下記を呼ぶ
      pocl_pthread_prepare_kernel()
        pocl_check_kernel_dlhandle_cache(): dlopen()
          pocl_check_kernel_disk_cache(): キャッシュ
            llvm_codegen(): ターゲットデバイスバイナリ生成
              pocl_llvm_codegen(): .so.oバイナリ作成
              pocl_invoke_clang(): .soバイナリ作成

独自アクセラレータ向け実装もホストCPU向け実装に習いclEnqueueNDRangeKernel() を起点にバイナリに変換を行います。現状はキャッシュ機構はスキップし、毎回ターゲットバイナリを生成する実装です。このままだと非常に遅いので今後、改善する必要があります。

独自アクセラレータ向け実装(仮)の呼び出し関係
clEnqueueNDRangeKernel()
  pocl_command_enqueue()
    pocl_accel_submit(): ここはターゲットデバイスごとに実装が異なる
      scheduleCommands()
        pocl_exec_command()
          pocl_accel_run(): ここから先はターゲットデバイスごとに実装が異なる
            llvm_codegen(): ターゲットデバイスバイナリ生成
              pocl_llvm_codegen(): .so.oバイナリ作成
              pocl_invoke_clang(): .soバイナリ作成

この関数の実行中に2つ一時ファイルが出力されます。本来は消されてしまって残らないファイルもありますが、コードを変更し一時ファイルをあえて残すと、

  • tempfile-xx-xx-xx-xx-xx.so.o: 中間バイナリ
  • XX/XXXXXXXX/kernelName/xx-x-x/kernelName.so: 最終的なOpenCLカーネルバイナリ

が作成されます。中間バイナリと最終バイナリの差は、C言語のコンパイルのときに生成する *.oと実行ファイルとの違いと同様に、前者はアドレスが解決されておらず、後者はアドレス解決済みという差がありました。他にも何か違いがあるかもしれません。

第1段階で紹介した通りclBuildProgram() のlink() にてOpenCLカーネルに必要なシンボルが集められます。そのため中間バイナリと最終バイナリを比較しても、含まれる関数やシンボルはほぼ変わりません(例外はclang_rt.builtinsに依存した関数、例えば __adddf3() など)。

編集者:すずき(2023/09/24 11:58)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2021年7月1日

OpenCLのOSS実装poclを調べる その6 - OpenCLカーネルのビルド

目次: OpenCL

引き続き、独自アクセラレータのテンプレート実装pocl/lib/CL/devices/accelの細かな問題を調べます。初期化を突破するとカーネルのビルドclBuildProgram() でコケます。

clBuildProgram() でエラー
POCL: in fn compile_and_link_program at line 664:
  |     ERROR | CL_COMPILER_NOT_AVAILABLE Cannot build a program from sources with pocl that does not have online compiler support

原因はdev->compiler_availableがCL_TRUEになっていないことです。初期化が足りないようです。

compiler_availableがfalseのときの動作

// pocl/lib/CL/pocl_build.c

cl_int
compile_and_link_program(int compile_program,
                         int link_program,
                         cl_program program,
                         cl_uint num_devices,
                         const cl_device_id *device_list,
                         const char *options,
                         cl_uint num_input_headers,
                         const cl_program *input_headers,
                         const char **header_include_names,
                         cl_uint num_input_programs,
                         const cl_program *input_programs,
                         void (CL_CALLBACK *pfn_notify) (cl_program program,
                                                         void *user_data),
                         void *user_data)
{

...

      /* clCreateProgramWithBuiltinKernels */
      /* No build step supported at the moment for built-in kernels. */
      if (program->builtin_kernel_names)
        continue;

      /* clCreateProgramWithSource */
      else if (program->source)
        {
#ifdef OCS_AVAILABLE
          if (device->compiler_available == CL_TRUE)    //★このif文が成立せず
            {
              POCL_MSG_PRINT_INFO ("building from sources for device %d\n",
                                   device_i);
              error = pocl_llvm_build_program (
                  program, device_i, program->compiler_options,
                  program_bc_path, num_input_headers, input_headers,
                  header_include_names, (create_library ? 0 : link_program));
              POCL_GOTO_ERROR_ON ((error != 0), build_error_code,
                                  "pocl_llvm_build_program() failed\n");
            }
          else
#endif
            {    //★こちらにきてエラーになってしまう
              APPEND_TO_MAIN_BUILD_LOG (
                  "Cannot build a program from sources with pocl "
                  "that does not have online compiler support\n");
              POCL_GOTO_ERROR_ON (1, CL_COMPILER_NOT_AVAILABLE, "%s",
                                  program->main_build_log);
            }
        }

他の実装を眺めるとpocl_init_default_device_infos() を呼んで解決しているようなので、先達に習いpocl_accel_init() でpocl_init_default_device_infos() を呼び出すように書き換えましょう。

その他のビルド関連の初期化

もともとあったdev->version, dev->available, dev->profileの初期化はpocl_init_default_device_infos() が行いますから、削除しても良いかもしれません。残っていても特に害はないと思いますけど。

現状では拡張機能dev->extensions = "cl_khr_fp64" を設定しないとdouble型を使ったときにビルドエラーになります。最終的に必要な拡張機能がはっきりするまでは必要になったものを順次追加する形で、次に進みましょう。

拡張機能はただ書けば動くわけではなくOpenCL Cコンパイラが対応している必要があります。当たり前ですね。poclが使っているコンパイラは大御所LLVMですから、RISC-V向けだけ機能が欠けていることはないでしょう。たぶん。

ビルド対象はRISC-Vにしたいので、

llvm_traget_tripletとllvm_cpuの設定値

dev->llvm_target_triplet = "riscv32";
dev->llvm_cpu = "generic-rv32";

としました。llvm_target_tripletはclang -cc1の -tripleオプションに渡されます。またllvm_cpuは -target-cpuオプションに渡されます。有効な値を調べる方法は、

llvm_target_tripletの調べ方
$ clang -print-targets

  Registered Targets:
    aarch64    - AArch64 (little endian)
    aarch64_32 - AArch64 (little endian ILP32)
    aarch64_be - AArch64 (big endian)
    amdgcn     - AMD GCN GPUs
...
    riscv32    - 32-bit RISC-V
    riscv64    - 64-bit RISC-V
    sparc      - Sparc
    sparcel    - Sparc LE
    sparcv9    - Sparc V9
    systemz    - SystemZ
...
llvm_cpuの調べ方
$ clang -target=riscv32 -print-supported-cpus

Debian clang version 11.0.1-2
Target: riscv32
Thread model: posix
InstalledDir: /usr/bin
Available CPUs for this target:

        generic-rv32
        generic-rv64
        rocket-rv32
        rocket-rv64
        sifive-e31
        sifive-u54

Use -mcpu or -mtune to specify the target's processor.
For example, clang --target=aarch64-unknown-linux-gui -mcpu=cortex-a35

GCCはコマンド名がアーキテクチャ名そのものですし、LLVMはヘルプで対応アーキテクチャがわかります。どちらも親切で良いですね。

仮実装

ここまでの変更を反映すると、

初期化関数(仮)

// pocl/lib/CL/device/accel/accel.cc

cl_int pocl_accel_init(unsigned j, cl_device_id dev, const char *parameters) {

  AccelData *D = new AccelData;
  dev->data = (void *)D;

  pocl_init_default_device_infos (dev);

  //SETUP_DEVICE_CL_VERSION(1, 2);    //★pocl_init_default_device_infosが初期化するのでいらない
  dev->type = CL_DEVICE_TYPE_CUSTOM;
  dev->long_name = (char *)"memory mapped custom device";
  dev->vendor = "pocl";
  //dev->version = "1.2";        //★pocl_init_default_device_infosが初期化するのでいらない
  //dev->available = CL_TRUE;    //★pocl_init_default_device_infosが初期化するのでいらない

  dev->extensions = "cl_khr_fp64";    //★cl_khr_fp64を追加
  //dev->profile = "FULL_PROFILE";     //★pocl_init_default_device_infosが初期化するのでいらない
  dev->max_mem_alloc_size = 100 * 1024 * 1024;

  dev->llvm_target_triplet = "riscv32";    //★追加
  dev->llvm_cpu = "generic-rv32";          //★追加

  dev->final_linkage_flags = final_ld_flags;

  if (!parameters) {
    POCL_ABORT("accel: parameters were not given\n");
  }

まだまだ変更が必要ですが、こだわるのは後にして次に進みます。

編集者:すずき(2023/09/24 11:58)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2021年6月24日

プログラムからLLVMを実行する その2 - プリプロセス編

目次: LLVM

準備が終わりましたらClang/LLVMをプログラムから呼びましょう。

LLVMでプリプロセスだけを実行するプログラム

int main(int argc, char *argv[])
{
	bool success;

	clang::CompilerInstance CI;
	clang::CompilerInvocation &build = CI.getInvocation();

	// 引数の配列を作成する
	std::vector<const char*> vec_args;
	vec_args.push_back("-I/usr/include/c++/10");
	vec_args.push_back("-I/usr/include/x86_64-linux-gnu/c++/10");
	vec_args.push_back("-I/usr/include/c++/10/backward");
	vec_args.push_back("-I/usr/lib/llvm-11/lib/clang/11.0.1/include");
	vec_args.push_back("-I/usr/include/x86_64-linux-gnu");
	vec_args.push_back("-I/usr/include");
	vec_args.push_back("-I/path/to/llvm-project/_install/include");

	// エラーメッセージを出力するために使われるクラス
	llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagID = new clang::DiagnosticIDs();
	llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> diagOpts = new clang::DiagnosticOptions();
	clang::TextDiagnosticBuffer *diagBuffer = new clang::TextDiagnosticBuffer();
	clang::DiagnosticsEngine diags(diagID, diagOpts, diagBuffer);
	CI.createDiagnostics(diagBuffer, false);

	// コンパイラ呼び出し用のインスタンスを作成する
	llvm::ArrayRef<const char*> ref_args(vec_args.data(), vec_args.data() + vec_args.size());
	success = clang::CompilerInvocation::CreateFromArgs(build, ref_args, diags);

	// コンパイラフロントエンドのオプション設定
	//   入力ソースコード: test.cpp
	//   出力ソースコード: test.preproc.cpp
	const char *source_file = "test.cpp";
	const char *preproc_file = "test.preproc.cpp";
	clang::FrontendOptions &fe   = build.getFrontendOpts();
	clang::InputKind ik          = clang::InputKind(clang::Language::CXX);
	clang::FrontendInputFile fif = clang::FrontendInputFile(source_file, ik);

	fe.Inputs.clear();
	fe.Inputs.push_back(fif);
	fe.OutputFile.assign(preproc_file);

	// プリプロセスのオプション設定
	//   言語: C++11
	clang::PreprocessorOptions &po = build.getPreprocessorOpts();
	clang::LangOptions *la         = build.getLangOpts();
	llvm::Triple triple            = llvm::Triple();
	build.setLangDefaults(*la, ik, triple, po.Includes, clang::LangStandard::lang_cxx11);

	// 下記のようにオプションの一部だけ変えることもできる
	//la->CPlusPlus = true;
	//la->CPlusPlus11 = true;

	// プリプロセスのオプション
	//   コメント、定義済みマクロなどは出力しない
	clang::PreprocessorOutputOptions &poo = build.getPreprocessorOutputOpts();
	poo.ShowCPP = true;
	poo.ShowComments = false;
	poo.ShowLineMarkers = false;
	poo.ShowMacros = false;
	poo.ShowMacroComments = false;
	poo.RewriteIncludes = false;

	// プリプロセス実行(失敗したらエラーログを出力する)
	clang::PrintPreprocessedAction Preprocess;
	success = CI.ExecuteAction(Preprocess);
	if (!success) {
		get_build_log(diagBuffer, (CI.hasSourceManager()) ? &CI.getSourceManager() : nullptr);
	}
}

残念ながらこの呼び出し方が正解とは断言できません。探した限りではどう呼び出すべきか書かれたドキュメントも見当たりませんでした。上記の例はpoclを参考にしており、大きな間違いはないはずですが……。何かやらかしていたら教えていただけると嬉しいです。

動作確認はLLVM 12で行いました。他のバージョンだとAPIの引数などが変わっているので、ビルドすら通らないと思います。LLVMの困ったところですね……。

インクルードパスの調べ方

上記のサンプルでは引数で -Iオプションを使ってインクルードパスを指定します。インクルードパスは頑張ってヘッダファイルがある場所を調べても良いですが、おそらく同じ名前のヘッダが複数の場所にあって混乱すると思いますから、PCで動作しているClang++ から拝借するのが簡単です。

clangのインクルードパスを調べる
$ clang++ test.cpp -v

Debian clang version 11.0.1-2
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/10

...

#include "..." search starts here:
#include <...> search starts here:
 /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10
 /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/x86_64-linux-gnu/c++/10
 /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/backward
 /usr/lib/llvm-11/lib/clang/11.0.1/include
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.

...

いろいろなメッセージが出力されますが、インクルードパスは "search starts here:" の辺りに書かれています。出力は特に捻りはなくディレクトリ名そのものですので、頭に -Iを足せばオプションの出来上がりです。

実行

プリプロセスを実行します。テスト用のプログラムは下記のとおりです。

テスト用のプログラム

#include <iostream>

int main(int argc, char *argv[])
{
	// This is comment
	std::cout << "Hello, world!!" << std::endl;
}

プリプロセス実行
$ make

$ ./clang_test

ファイル名などは完全に決め打ちのため引数は必要ありません。実行に成功するとプリプロセス後のソースコードtest.preproc.cppが作成されているはずです。

プリプロセス後のソースコード

namespace std
{
  typedef long unsigned int size_t;
  typedef long int ptrdiff_t;


  typedef decltype(nullptr) nullptr_t;

}

...

  static ios_base::Init __ioinit;


}

int main(int argc, char *argv[])
{

 std::cout << "Hello, world!!" << std::endl;
}

私の環境で実行したところ27,000行くらいあるファイルになりました。たった1つしかヘッダをincludeしてないのに凄まじい行数に展開されます。コメントは消えていますが、オプションを変更すれば残すこともできます。PreprocessorOutputOptionsのShowComments = trueにすると残ります。

プリプロセス後のソースコードをビルド&実行
$ g++ test.preproc.cpp

$ ./a.out
Hello, world!!

プリプロセス後のソースコードをg++ などに渡すとコンパイル可能なので、おそらく変な出力にはなっていないでしょう。

編集者:すずき(2023/09/24 09:16)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2021年6月23日

プログラムからLLVMを実行する その1 - 準備編

目次: LLVM

LLVMやClangは実行する方法が2つあります。1つ目はみなさまお馴染みのコマンドラインから実行する方法で、2つ目はプログラムからClangのライブラリを通して実行する方法です。

特に後者のプログラムから実行する方法はGCCでは真似できませんから、LLVMならではの機能と言えるでしょう。ただ、ちょっとインタフェースが不安定というか、バージョンによってちょいちょい変わって動かなくなるようで、そこは玉に瑕ですね。

LLVMビルド&インストール

Clang/LLVMをプログラムから実行するにはいくつか準備が必要です。大まかに分けるとLLVMのビルド&インストールと、ヘッダおよびライブラリパスの指定です。

ビルドは以前もチャレンジしました(2019年3月26日の日記参照)。基本的にはcmakeとmake(またはninja)です。それは変わりませんが、いくつか追加したいオプションがあるので再掲します。

LLVMのビルドオプション
$ cmake \
  -G Ninja \
  ../llvm \
  -DCMAKE_INSTALL_PREFIX=`pwd`/../_install \
  -DCMAKE_C_COMPILER=clang \
  -DCMAKE_CXX_COMPILER=clang++ \
  -DCMAKE_BUILD_TYPE=RelWithDebInfo \
  -DBUILD_SHARED_LIBS=ON \
  -DLLVM_ENABLE_ASSERTIONS=ON \
  -DLLVM_TARGETS_TO_BUILD="X86;RISCV;NVPTX" \
  -DLLVM_USE_LINKER=lld \
  -DLLVM_BUILD_LLVM_DYLIB=OFF \
  -DLLVM_LINK_LLVM_DYLIB=OFF \
  -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;compiler-rt;debuginfo-tests;libc;libclc;libcxx;libcxxabi;libunwind;lld;lldb"

ざっくり意図を説明すると下記のとおりです。オプションの正確な意味についてはLLVM公式ドキュメント(Build LLVM with CMake - LLVM 12 documentation 参照)を見てください。

CMAKE_INSTALL_PREFIX
インストール先を指定します。システムに既にインストールされているLLVMを破壊しないよう、ビルドディレクトリの隣の _installディレクトリにインストールする指定です。
LLVM_TARGETS_TO_BUILD
以前(2019年3月27日の日記参照)も使いましたが、特定ターゲットのみをビルドするオプションで、ビルド時間の短縮に繋がります。2つ以上指定する場合はセミコロンで繋ぎましょう。例ではx86とRISC-V向けにしていますが、お好きなアーキテクチャを足してください。
LLVM_BUILD_LLVM_DYLIB
全てのライブラリを1つのライブラリlibLLVM.soに集約するオプションです。興味があればこのオプションのON/OFFにより後述するllvm-configの出力がどう変化するか確認すると面白いかもしれません。
LLVM_ENABLE_PROJECTS
LLVMはLLVM以外にも多彩なツールを持っています。どのツールをビルドするか選択するオプションです。全ては必要ないですが少なくともclangは後で必要になります。例では全部入りにしています。

CMakeの実行が成功したら、ninja installを呼びましょう。インストールまで進むはずです。

Makefileの作成

ヘッダインクルードパスの指定、ライブラリパスの指定のためにMakefileを書きます。パスの細かい値について心配する必要はありません。llvm-configというツールが用意されており、ほぼ全て自動的に用意してくれます。Makefileの一例を示すと、

テスト用のMakefile

LLVM_CONFIG_PATH  = /path/to/llvm-project/_install/bin
LLVM_CONFIG       = $(LLVM_CONFIG_PATH)/llvm-config --link-shared

CPPFLAGS = $(shell $(LLVM_CONFIG) --cppflags)
CFLAGS   = $(shell $(LLVM_CONFIG) --cflags) -g
CXXFLAGS = $(shell $(LLVM_CONFIG) --cxxflags) -g
LDFLAGS  = $(shell $(LLVM_CONFIG) --ldflags) 
LIBS     = -lclang-cpp $(LLVM_CONFIG) --libs --system-libs engine)

clang_test: main.o
	$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(APP) $< $(LIBS)

基本的にはllvm-config --xxxflagsとするとオプションに指定すべき文字列が出力されますから、素直に各種FLAGSに渡すだけです。もちろん何かオプションを追加するのも自由です。例では -gを足しています。

LIBSのところがちょっと格好悪いのは、llvm-configでlibclang-cppにリンクするような方法が見当たらなかったからです。良い方法をご存知の方は教えていただけると嬉しいです。

これで準備完了です。続きは次回に。

編集者:すずき(2023/09/24 09:16)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2021年6月21日

mplayer, mpvでイコライザーを使う

目次: ALSA

いつもわからなくなるのでメモしておきます。mplayerにてイコライザーを使う方法です。最近はmpvと呼ぶんですかね?

コマンドはmpvを使いますが、実はイコライザー機能はffmpegの一部であるlibavfilter.soに頼っています(avfilterのドキュメントへのリンク)。この構造は一見しただけではわかりにくく、ヘルプを探すときに非常に難儀しました。設定方法も独特でいつも書き方がわからなくなります。

イコライザーはsuperequalizerという名前です(superequalizerのドキュメントへのリンク)。18バンド指定できます。各バンドがどの周波数帯に対応するかはドキュメントを見てください。

mpvでavfilterのsuperequalizerを設定する例
$ mpv --no-video --af=volume=0.8,superequalizer=1.2:1.5:1.5:1.2:1.2:1:1:1:1:1:1:1:1:1:1:1:1:1 a.mp4

     Video --vid=1 (*) (h264 480x360 6.000fps)
 (+) Audio --aid=1 (*) (aac 2ch 44100Hz)
AO: [pulse] 44100Hz stereo 2ch float
A: 00:00:01 / 00:04:40 (0%) Cache: 278s/9MB

上記の例では、映像を出さない(--no-video)、音割れ防止の為にvolumeで8割くらいに音を下げる、superequalizerの18バンドを全て設定しています。superequalizer=1b=1.2:2b=1.5のようにすると特定のバンドだけ設定変更できます。便利な方を使ってください。

mpvのバージョン
$ mpv --version

mpv 0.32.0 Copyright © 2000-2020 mpv/MPlayer/mplayer2 projects
 built on UNKNOWN
ffmpeg library versions:
   libavutil       56.51.100
   libavcodec      58.91.100
   libavformat     58.45.100
   libswscale      5.7.100
   libavfilter     7.85.100
   libswresample   3.7.100
ffmpeg version: 4.3.2-0+deb11u2

動作確認に使ったmpvのバージョンも記録しておきます。なぜならffmpegやmpvはたまにインタフェースが激変するので、将来的に同じ方法が通用しなくなる可能性が高いからです。使用しているディストリビューションはDebian Testingです、今はDebian 11相当みたいですね。

なぜかbuilt on UNKNOWNになっていて若干気になりますけど、特に害なさそうだから良いのかな……。

編集者:すずき(2023/01/23 14:10)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2021年6月20日

読書一生分

書籍通販のhontoがこんなキャンペーンをやっています。


honto読書一生分プレゼントキャンペーン

このキャンペーン画像を見たときの率直な感想としては、どんな人間を想定したら、読書一生分がたった93万円に収まるのか?でした。マンガしか読んでない自分でさえ100万じゃ10年も持ちません。

1世帯あたり読書にいくら使う?

思い込みで文句を言うのは良くないなと思って、統計データを見ました。総務省統計局 - 読書に関する支出(2018年)によると、1世帯、読書の支出が年間10,628円(電子書籍含まず)です。電子書籍を含む値段で考えたとしても、さほど変わりません。電子書籍を最も購入している30代(世帯主の年齢)でも1,736円で、読書支出は12,000円程度だからです。

世帯の読書支出10,628円x日本人の平均寿命84年 = 892,752円となり、hontoのキャンペーン金額と大体同じくらいになります。あながち間違った数値でもなかった、ということですね。

1人あたり読書にいくら使う?

先程のデータを見ていて何が驚いたって、1世帯で1年間たった1万円しか本を買わないことです。この時点で少ないなと思うんですけど……。1世帯には複数人が生活していますので、1人あたりの支出も計算してみます。

世帯の平均人数はe-Statで調べることができます。平均世帯人員、年次別(平成27年国民生活基礎調査 世帯票 報告書掲載 年次推移 表番号7)を見ると、2015年で1世帯平均2.49人です。

世帯あたり読書の支出は1年10,628円(書籍7,478円、雑誌3,150円)割ることの、日本の平均世帯人数2.49人(減少傾向)ですから、1人あたり1年で4,268円(書籍3,003円、雑誌1,265円)です。さらに少なくなりました。

例えば、週刊少年ジャンプ(定価270円x 50冊 = 13,500円)をもれなく買うだけで3倍以上の支出になります。普段全く本は買わない、くらいじゃないと1年4,268円は厳しいです。世間の生活が想像できません……。

編集者:すずき(2021/06/25 12:33)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2021年7月3日 >>> 2021年6月20日
link もっと後

管理用メニュー

link 記事を新規作成

<2021>
<<<07>>>
----123
45678910
11121314151617
18192021222324
25262728293031

最近のコメント5件

  • link 21年3月13日
    すずきさん (03/05 15:13)
    「あー、このプログラムがまずいんですね。ご...」
  • link 21年3月13日
    emkさん (03/05 12:44)
    「キャストでvolatileを外してアクセ...」
  • link 24年1月24日
    すずきさん (02/19 18:37)
    「簡単にできる方法はPowerShellの...」
  • link 24年1月24日
    KKKさん (02/19 02:30)
    「追伸です。\nネットで調べたらマイクロソ...」
  • link 24年1月24日
    KKKさん (02/19 02:25)
    「私もエラーで困ってます\n手動での回復パ...」

最近の記事3件

  • link 24年3月25日
    すずき (03/26 03:20)
    「[Might and Magic Book One TASのその後] 目次: Might and Magicファミコン版以前(...」
  • link 21年10月4日
    すずき (03/26 03:14)
    「[Might and Magicファミコン版 - まとめリンク] 目次: Might and Magicファミコン版TASに挑...」
  • link 24年3月19日
    すずき (03/20 02:52)
    「[モジュラージャックの規格] 古くは電話線で、今だとEthernetで良く見かけるモジュラージャックというコネクタとレセプタク...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 03/26 03:20