ESP32 の命令実行サイクル数 (1)

gcc のインライン・アセンブラを使って Xtensa LX6 の「MAC16 拡張命令」を記述し、実行サイクル数の削減を試みていますが、次の 2 つの理由で成功していません。

  • インライン・アセンブラを利用すると、「ハードウェア・ループ命令」が使われなくなり、通常通りのループカウンタ・デクリメント + 条件ジャンプの組み合わせでループが構成され、5 サイクルのオーバーヘッドが生じる
  • MAC16 拡張命令の実行ではパイプライン・ストールが生じ、1 命令 1 サイクルとはならない

ハードウェア・ループ命令のアセンブラシンタックスは、次のようになっています。

    LOOP    as, label

「as」は「汎用レジスタ」で、指定したレジスタの内容をループ・カウンタの初期値として使用します。
「label」は、ループ範囲外の最初の命令に付けたラベル、つまりループ範囲の最後の命令の次の命令に付けたラベルを指定します。
命令中では PC 相対の 8 ビット・オフセットで表現されているので、ループ範囲のサイズは 256 バイト以下となります。
LOOP 命令では、スペシャル・レジスタである LBEG, LEND, LCOUNT を初期設定してハードウェア・ループの実行の準備をします。
ループ開始アドレスである LBEG には LOOP 命令 (3 バイト) の直後のアドレスを設定します。
LEND には LOOP 命令内の 8 ビット・オフセットと PC 値を加算して求めたルーブ外アドレスを格納します。
仕様上、ループの中身が「空」、つまり (LEND == LBEG) は指定できず、 (LEND >= (LBEG+1)) の必要があります。
LCOUNT には (as-1) の値を設定します。
as = 1 では、ループ範囲を1 回のみ実行 (LCOUNT = 0)、as = 2 ではループ範囲を 2 回実行 (LCOUNT = 1)、as = 0 では 232 = 4294967296 回のループ (LCOUNT = 0xFFFFFFFF) となります。 仕様上、0 回実行はありません。
ハードウェア・ループは命令フェッチ部分に作用し、パイプラインを乱すことなくループを実現しているので、本当にオーバーヘッドなし、つまり、ループ更新を「0 サイクル」で行います。
ただし、ネスティング可能なようには構成されていないので、多重のループの場合には、最内周のループにのみ適用できます。 外側のループは通常の方法で実現する必要があります。
ループ内部がすべて C 言語で記述されていて、関数呼び出しなどを含まず、256 バイト以内におさまり、LOOP 命令で実現可能な場合には LOOP 命令を使う形にコンパイルされるようですが、ループ内部にインライン・アセンブラ記述を含むと LOOP 命令は使用されなくなるようです。
コンパイラはインライン・アセンブラ記述に対しては引数の置き換えなどを行った後はアセンブラにそのまま渡すだけで、アセンブラ記述の中身まで見ているわけではないようです。
インライン・アセンブラ記述の内部で直接 LOOP 命令が使われていたり、関数の呼び出し先で LOOP 命令が使われていたり、ループ範囲が 256 バイトに収まらない「危険性」があるため、ループ内にインライン・アセンブラ記述があると一律に LOOP 命令を使用しなくなるようです。