最終更新日時(UTC):
が更新

履歴 編集

constexpr関数内で未評価のインラインアセンブリを許可することによる組み込み関数のconstexpr有効化(C++20)

概要

asm宣言によるインラインアセンブリはconstexpr関数内に現れる事が禁止されていたため、constexpr関数内に書く事は出来なかった。C++20からはコンパイル時に評価されない限り、asm宣言をconstexpr関数内に書く事ができるようになる。

#include <type_traits>

constexpr double fma(double a, double b, double c) {
  if (std::is_constant_evaluated()) {
    // コンパイル時のコード
    return a * b + c;
  } else {
    // 実行時のコード
    asm volatile ("vfmadd213sd %0,%1,%2" : "+x"(a) : "x"(b),"x"(c));
    return a;
  }
}

このように、is_constant_evaluatedと組み合わせて同じ関数で実行時とコンパイル時それぞれで最適な実装を選択する事ができるようになる。

ただし、コンパイル時に実行できるようになったわけではないため、asm宣言を評価するコードパスは実行時にのみ通過するようにしなければならない。

#include <iostream>
#include <iomanip>
#include <type_traits>
#include <numbers>

constexpr double inner_product_2d(const double (&v1)[2], const double (&v2)[2]) {
  double dp{};

  if (std::is_constant_evaluated()) {
    dp = v1[0]*v2[0] + v1[1]*v2[1];
  } else {
    asm volatile (
      "movlpd %1, %%xmm0\n"
      "movhpd %2, %%xmm0\n"
      "movlpd %3, %%xmm1\n"
      "movhpd %4, %%xmm1\n"
      "dppd $0b110001, %%xmm1, %%xmm0\n"
      "movlpd %%xmm0, %0\n"
      : "=m"(dp)
      : "m"(v1[0]), "m"(v1[1]), "m"(v2[0]), "m"(v2[1])
    );
  }

  return dp;
}

int main() {
  std::cout << std::setprecision(16);
  constexpr double v1_1[2] = { 2.0, 2.0 }; 
  constexpr double v1_2[2] = { 2.0, -2.0 };
  constexpr double v2_1[2] = { 0.0, 1.0 }; 
  constexpr double v2_2[2] = { 1.0, std::numbers::sqrt2 };

  constexpr double dp1c = inner_product_2d(v1_1, v1_2); // コンパイル時
  double  dp1r = inner_product_2d(v1_1, v1_2);          // 実行時

  std::cout << dp1c << "\n" << dp1r << std::endl;

  constexpr double dp2c = inner_product_2d(v2_1, v2_2); // コンパイル時
  double  dp2r = inner_product_2d(v2_1, v2_2);          // 実行時

  std::cout << dp2c << "\n" << dp2r << std::endl;
}

出力例(GCC 11.1 x86-64)

0
0
1.414213562373095
1.414213562373095

この機能が必要になった背景・経緯

asmによるインラインアセンブリはコンパイラの最適化を補完するなど、特定の処理を高速化する事を目的として利用される。しかし、そのようなコードは実行時にのみ評価可能であり、コンパイラの定数化の対象ではなく、そのままconstexpr関数にすることはできなかった。

この機能に先立ち、C++20にはその実行がコンパイル時と実行時のどちらで行われているのかを判定するis_constant_evaluatedが導入されていた。これを用いると、1つの関数定義でコンパイル時と実行時のそれぞれに最適な処理を選択する事ができる。しかし、asm宣言はconstexpr関数内で現れる事が禁止されていたため、インラインアセンブリによって最適化されたコードをis_constant_evaluatedを用いてconstexpr関数化する事は出来なかった。

特定のプロセッサ向けの組み込み関数や既存のasm宣言を用いたコードをconstexpr化したいという需要があり、is_constant_evaluatedによってそれが可能となったため、asm宣言が定数式で評価されない場合に限ってconstexpr関数内で書けないという制限を緩和する事となった。

副次的だが、複雑なインラインアセンブリによる記述とともにシンプルなC++コードによる記述が同居している事で、インラインアセンブリが何をしているのかをユーザーが理解しやすくなるという効果もある。

関連項目

参照