憂鬱な午後のひととき
disp8*N
やっと意味とエンコード方法が分かってきた.
62F1ED485808 vaddpd zmm1, zmm2, [rax]
62F1ED48584801 vaddpd zmm1, zmm2, [rax+0x40]
62F1ED585808 vaddpd zmm1, zmm2, [rax]{1to8}
62F1ED58584801 vaddpd zmm1, zmm2, [rax+8]{1to8}
{1to2},{1to4},{1to8}の3種類だけ.
ただしそれをするか否かの1bitの情報だけでレジスタサイズから1toNのNは決まる.
インテルのマニュアルには{b}を使っているところもあった.
{1toN}は冗長だからこちらのほうがいいな.
vcmppdサポート
vcmppd xmm1, xmm2, xmm3, 0
vcmppd ymm1, ymm2, ymm3, 0
ときたら
vcmppd zmm1, zmm2, zmm3, 0
だろうと思ったらzmmじゃなくてkだった.
vcmppd k4{k3}, zmm20, zmm30, {sae}, 5
に対応した.
メモリとsaeサポート
vaddpd(zmm1, zmm30, ptr [rax + rcx + 5]);
vaddpd(zmm2 | k1 | T_z, zmm2, zmm3 | T_rd_sae);
みたいなのが動くようになった.
EVEXでは32bit displacement固定なのね.
disp8*Nがどういうものかまだよく分かってない.
opmaskとzeroingサポート
vaddps zmm1{k1}{z}, zmm2, zmm3
をどう表現しようか悩んだが結局 | を使うことにした.
vaddps(zmm1 | k1 | T_z, zmm2, zmm3);
みたいな感じ. saeの方は素直にカンマを使おうとも思ったが, vcvtps2phみたいにその後ろに即値が来るパターンもあったので同様に | を使おうと思う.
vcvtps2ph ymm1, zmm2, 3
vcvtps2ph ymm1{k1}{z}, zmm2, {sae}, 3
もうちょっと何かよい文法は無かったのかと思う.
XbyakもそうだがNASMも不正な文法に対するチェックは甘いようで
vcvtps2ph ymm1{k1}{z}{z}{z}{z}, zmm2, {sae},{sae},{sae},{sae}, 3
が通ってる. まあ実害は無いけどね.
ARM続き
一番簡単なcarryなし固定長加算を実装してみた(low_armv7.s).
ldm, stmで連続するメモリを複数レジスタに一度に入れられたり, !でポインタを更新したりとかなり高機能.
とりあえず命令数が少ない方が速い感じ. メモリの測度にも影響しそうだけどそればっかりは分からん.
AVX-512 encoding
とりあえず"paddpd reg1, reg2, reg3"の形のもの.
zmmレジスタは32個あるので5bit必要. それぞれの指数を
reg1Idx = [~R':~R:r1] ; R, R'は1bit, r1は3bit, ~は否定
reg2Idx = [~V':~vvvv] ; V'は1bit, vvvvは4bit
reg3Idx = [~X:~B:r2] ; X, Bは1bit, r2は3bit
と表す. 名前が分かりにくいけどマニュアルがそうなってるのでしかたない.
p0 p1 p2
R X B R' fix mm W vvvv fix pp z L' L b V' aaa
0| 0| 1| 1| 00| 01| | 1| 1101| 1| 01| | 0| 1| 0| 0| 1| 000|
詰め込み具合が大変なことに. ちなみにregはxmm, ymmのときはL'Lはそれぞれ0, 1となる.
zはzero拡張フラグ, aaaはkレジスタのindexが入るのかな.
ARM事始め
LLVMのARM用コード生成が全然当てにならないことが分かったので自分で書くことにした.
SPARC, MIPS, TIのDSP, PowerPC, DAPDNA, CELLはやったことがあるがARMは初めてなのだった.
というわけで今更ながら勉強を始める.
マニュアルはRealView Compilation Tools アセンブラガイド バージョン 4.0
32bitの命令セットの名前がARM. Thumb, Thumb-2などの16bit命令セット拡張はあるけどとりあえず関係無さそう.
ARMは30本の32bit汎用レジスタ. ただし一度に見えるのはr0, ..., r12, sp(=r13), lr(=r14)らしい.
spがスタックポインタ, lrはリンクレジスタで復帰アドレスが入る.
APSRはアプリケーションプログラム状態レジスタ. x86のflagレジスタみたいなものか.
ALUステータスフラグに応じて変わる.
ALUステータスフラグ
N ; 負になった
Z ; zeroになった
C ; carry発生
V ; overflow
条件コード{cond}
条件コードフラグが条件を満たしたときに実行される
EQ ; Zセット ; 等しい
NE ; Zクリア ; 等しくない
CS or HS ; Cセット ; 符号無し>=
CC or L0 ; Cクリア ; 符号無し<
MI ; Nセット ; < 0
PL ; Nクリア ; >= 0
VS ; Vセット ; overflow
VC ; Vクリア ; overflowなし
HI ; Cセット & Zクリア ; 符号なし>
LS ; Cクリア | Zセット ; 符号なし<=
GE ; N != V ; 符号付き>=
LT ; N != V ; 符号付き<
GT ; Zクリア N = V ; 符号付き>
LE ; Zセット N != V ; 符号付き<=
AL ; すべて ; 無条件
一通りそろってる. 条件実行を使うとgcdのコード
int gcd(int a, int b)
{
while (a != b) {
if (a > b) {
a = a - b;
} else {
b = b - a;
}
return a;
}
は
gcd ; a = r0, b = r1
cmp r0, r1
beq end
blt less
subs r0, r0, r1
b gcd
less
subs r1, r1, r0
b gcd
end
とできると. なるほど.
手始めに
#include <stdint.h>
void add(uint64_t *z, const uint64_t *x, const uint64_t *y)
{
z[0] = x[0] + y[0];
}
をgccとclangで
add1: // gcc
ldrd r2, [r2]
stmfd sp!, {r4, r5}
ldrd r4, [r1]
adds r2, r2, r4
adc r3, r3, r5
ldmfd sp!, {r4, r5}
strd r2, [r0]
bx lr
.align 2
add2: // clang
ldm r1, {r3, r12}
ldm r2, {r1, r2}
adds r1, r1, r3
adc r2, r2, r12
stm r0, {r1, r2}
mov pc, lr
gccの方が速かった. stmfd, ldmfdはスタックにレジスタr4, r5を退避, 復元しているようだ.
引数は一つ目からr0, r1, r2. r3, r12は自由に使ってよさげ(本当か? 呼び出し規約が見当たらなかった).
bx lrとmov pc, lrの違いは何だろう. clangのmov pc, lrをbx lrにしたらgccのコードより速くなった.
特に問題なく動いているようだが.
疲れが溜まりすぎ
二日間, 体が全然動かなくてずっと寝ていた.
情報幾何学の基礎輪読会参加
計算お疲れさまでした. この本, 計算が結構迷走してるところがちらほらあって読みにくい.
たとえばp.77の(3.21)の導出はこんな回りくどいことしなくてもp.60の(3.5)の直前の式でΓabc = 0とすればすぐでる.
p.78の導出もp.73とほとんど同じ.
ぼちぼち自分の番が回ってくるな. 準備しないと.
Xbyakのリファクタリング
AVX-512のEVEXエンコーディングに対応するため, Addressクラスまわりのリファクタリング.
32bit→64bit→AVXと複雑怪奇な拡張にともなって自分のコードもぐちゃぐちゃになっていた.
2007年から始めてるんだな. 思えば相当長い.
PBWT-sec with mcl
早稲田大学の清水さん、産総研の縫田さん、MSKCCのGunnarさんたちによるEfficient Privacy-Preserving String Search and an Application in Genomicsの実装.
私が今開発してるmclを使うpull reqを取り込んでもらった.
ドキュメントにある簡単なベンチマークで
従来版
23-mer match
00000100010000010000011
client(Wall): 68.9388
c2s, s2c(Mbyte): 1.8, 1.81
13.944u 0.184s 1:08.94 20.4% 0+0k 0+8072io 0pf+0w
最新版
23-mer match
00000100010000010000011
client(Wall): 14.7389
c2s, s2c(Mbyte): 1.84, 1.84
1.544u 0.296s 0:14.74 12.4% 0+0k 0+8072io 0pf+0w
となった.
早稲田
初めて行った. 会社からは東西線だと一本で行けるけど雨がきつかったので西早稲田から.
Opmaskレジスタ関連命令実装完了
次はいよいよEVEXまわりの実装. マニュアル読んでもなかなか頭にはいらない.
Xbyakのコードもリファクタリングしないとかなり辛い.
『トーキョー・プリズン』(柳広司)
戦後の東京裁判にかかえられる囚人がいる刑務所での事件.
ホームズに相当する人物が囚人でニュージーランド将校がワトソン役というのが斬新.
冒頭に感じた面白い話ではなく予想外の方向に進んだ.
VCでarm
そういえば最近のVCにはarmのクロスコンパイルツールも入ってるのだった.
どんなものなんだろう.
32bit環境で試してみる.
#include <stdint.h>
void add(uint64_t *z, const uint64_t *x, const uint64_t *y)
{
z[0] = x[0] + y[0];
}
push {r4,lr}
ldrd r4,r1,[r1]
ldrd r3,r2,[r2]
adds r3,r3,r4
adcs r2,r2,r1
strd r3,r2,[r0]
pop {r4,pc}
おお, ちゃんとでてる. これはどうやらthumbというモードのコードのようだ.
clangの-march=thumbだと
push {r4, lr}
ldr r3, [r1]
ldr r1, [r1, #4]
ldr r4, [r2]
ldr r2, [r2, #4]
adds r3, r4, r3
adcs r2, r1
str r3, [r0]
str r2, [r0, #4]
pop {r4}
pop {r0}
bx r0
VCの方がよい感じに見えるなあ.
ARM Intrinsicsを見るとadcに相当する関数は無さそうだ.
残念.
ただそもそも_arm_smlalとか呼んでも無いって言われてしまう.
何かオプションが必要なのだろうか.
Opmaskレジスタ
XbyakのAVX-512対応を少しずつ始める.
とりあえず簡単なkレジスタの対応から.
マニュアルを見るとVEXエンコードも少し拡張されていた.
AVX2
VEX.[NDS/NDD/DS].[128,256,LZ,LIG].[66,F2,F3].0F/0F3A/0F38.[W0,W1,WIG] opcode [/r][ib,/is4]
AVX-512
VEX.[NDS/NDD/DS].[128,256,L0,L1,LIG].[66,F2,F3].0F/0F3A/0F38.[W0,W1,WIG] opcode [/r][ib,/is4]
L0やL1が増えている. kレジスタはビット長の情報は不要なのでそこを利用するようだ.
kadd, kand, kandn, kor, kxnor, kxorのb, w, d, qサフィックス版がある.
たとえばkxnor k1, k2, k3.
ぱっとみ, もはやIntel系CPUの命令とは思えない….
Re: 可変長マクロのコンマの扱いについてのトリック
##を使うとコンマが消えるのはトリックじゃなくてgcc/clangの拡張(Variadic Macros)と教えていただきました.
ありがとうございます. 適当なこといってすいませんでした.
実際mcpp -- a portable C preprocessorで試すと
t.c:3: error: Not a valid preprocessing token ",1"
macro "F" defined as: #define F(msg,...) fprintf(fp, msg,## __VA_ARGS__) /* t.c:1 */
from t.c: 3: F("c=%d\n", 1);
というエラーになってました. 発言する前に確認しておくべきだった. 反省.
可変長マクロのコンマの扱いについてのトリック
可変長マクロで
#define F(msg, ...) fprintf(fp, msg, __VA_ARGS__)
F("c=%d\n", 1);
F("a=%d, b=%d\n", 2, 3);
という使い方をよくするけれどもF("xyz\n")とすると
fprintf(fp, "xyz\n", );
と最後に余計なコンマがついてエラーになってしまう.
Comma omission and comma deletionによると, これを避けるためには##を使うとよいらしい.
#define G(msg, ...) fprintf(fp, msg, ## __VA_ARGS__)
G("c=%d\n", 1);
G("a=%d, b=%d\n", 2, 3);
G("xyz\n");
>clang t.c -E
fprintf(fp, "c=%d\n", 1);
fprintf(fp, "a=%d, b=%d\n", 2, 3);
fprintf(fp, "xyz\n");
本当だ. コンマが消えてる. 不思議だ.
ちなみにVisual StudioはもとのFでも気を利かせて余計なコンマを取り除いてくれる.
##を使っても問題ない.
『反コミュニケーション』(奥村隆)
さらっとしか読んでないけど面白かった.
のっけから著者が私はコミュニケーションが嫌いだというのに面食らう.
本当にコミュニケーションをよくしたいのか, 本当に全部分かり合いたいのか, よいって何だという疑問.
各哲学者にインタビューしに行くという形式や最後の学生が書いた架空のレポートに対する討論も面白い.
1908年ジンメルの『社会学』によると
>「すべてのものはすべてのものとなんらかの相互作用の状態にある」
>存在するのは「個人」でも「社会」でもなく, 「相互作用」だけである.
だそうな. 場の量子論や素粒子物理学より前に人間に対してそういう考えをしていたんだ. へえ.
LLVM RAに対するオプション
@nothingcosmosさんがllc -O3 -pre-RA-sched=list-ilp -max-sched-reorder=128だと
add1024が期待通りのコードを生成してくれる(arm以外)と教えてくださった.
armで駄目なのは痛いけどありがとうございます.
RAはregister allocationの略. pre-RA-schedはCPUに応じたレジスタ割り当て最適化をする前の令のスケジューリングを設定するオプションのようだ.
ILP(instruction level parallelism)とレジスタプレッシャーのバランス(ループアンロールしたらレジスタ足りないみたいなの)を変える.
_addcarry_u64の使い方
1日の_addcarry_u64はすばらしいが現状のVSで使うにはちょっと一工夫がいる.
たとえば(64 * N)-bit加算関数を作ろうと
template<int N>
void add1(uint64_t *pz, const uint64_t *px, const uint64_t *py)
{
unsigned char c = 0;
for (int i = 0; i < N; i++) {
c = _addcarry_u64(c, px[i], py[i], &pz[i]);
}
}
とするとN = 2ですらよくないコードが生成される.
xor r9b, r9b
mov r10d, 2
sub r8, rdx
.lp:
mov rax, QWORD PTR [rdx]
lea rcx, QWORD PTR [rcx+8]
add r9b, -1
adc rax, QWORD PTR [r8+rdx]
lea rdx, QWORD PTR [rdx+8]
mov QWORD PTR [rcx-8], rax
setb r9b
sub r10, 1
jne .lp
ret 0
おいおいループ展開されないのかよと3年近く前にも文句を言ったがVS2015でも改善されていない.
こういうのは今までも何度か書いたけど次のようなループ展開を強制するtemplateクラスを作れば回避できる.
template<class P, int begin, int end, int step = 1, int count = (step > 0 ? (end - begin + step - 1) / step : (begin - end - step - 1) / -step), int i = count>
struct Loop {
Loop(P& p)
{
p.template calc<begin + (count - i) * step>();
Loop<P, begin, end, step, count, i - 1> run(p);
}
};
template<class P, int begin, int end, int step, int count>
struct Loop<P, begin, end, step, count, 0> {
Loop(P&) { }
};
使い方はtemplate<int i>void cal()を持つクラスPを用意してcalcの中でやりたい処理を書く.
#include <intrin.h>
#include <stdint.h>
struct P {
uint64_t *pz;
const uint64_t *px;
const uint64_t *py;
unsigned char c;
template<int i>
void calc()
{
c = _addcarry_u64(c, px[i], py[i], &pz[i]);
}
};
template<size_t N>
void add(uint64_t *pz, const uint64_t *px, const uint64_t *py)
{
P p;
p.pz = pz;
p.px = px;
p.py = py;
p.c = 0;
typedef Loop<P, 0, N> Lp;
Lp run(p);
}
たとえばN = 8のときはこういうコードになる.
add:
mov rax, [rdx]
add rax, [r8]
mov [rcx], rax
mov rax, [rdx+8]
adc rax, [r8+8]
mov [rcx+8], rax
mov rax, [rdx+16]
adc rax, [r8+16]
mov [rcx+16], rax
mov rax, [rdx+24]
adc rax, [r8+24]
mov [rcx+24], rax
mov rax, [rdx+32]
adc rax, [r8+32]
mov [rcx+32], rax
mov rax, [rdx+40]
adc rax, [r8+40]
mov [rcx+40], rax
mov rax, [rdx+48]
adc rax, [r8+48]
mov [rcx+48], rax
mov rax, [rdx+56]
adc rax, [r8+56]
mov [rcx+56], rax
ret
これがgccやclang, 他のCPUでもできればなあ.
LLVMの隠しオプション
1日のadd1024は実はxの値を2倍する場合はきれいな値を出力する.
define void @add1024(i1024* noalias %pz, i1024* noalias %px)
{
%x = load i1024, i1024* %px
%z = add i1024 %x, %x
store i1024 %z, i1024* %pz
ret void
}
// llc-3.8 -o - a.ll -O3 -march=x86 -x86-asm-syntax=intel
add1024:
mov eax, dword ptr [esp + 4]
mov ecx, dword ptr [esp + 8]
mov edx, dword ptr [ecx]
add edx, edx
mov dword ptr [eax], edx
mov edx, dword ptr [ecx + 4]
adc edx, edx
mov dword ptr [eax + 4], edx
mov edx, dword ptr [ecx + 8]
adc edx, edx
...
ただし引数が互いにオーバーラップしていないということを伝えるnoaliasを消すとデータを退避するコードになる.
z = x + yの場合はnoaliasに関わらずうまくいかない.
x + xがうまくいくのだったらx + yをうまく処理してくれたってよいだろう.
ただそういう最適化パスが入っていないわけではないと.
というわけでx + xのDAG(コードの木構造)がnoaliasありなしで違いが出るところを観察しようとした.
SelectionDAG Instruction Selection Processを見ながら-view*なんとか系でDAGを出力させてみようとする.
-view-dag-combine-lt-dags - Pop up a window to show dags before the post legalize types dag combine pass
-view-dag-combine1-dags - Pop up a window to show dags before the first dag combine pass
-view-dag-combine2-dags - Pop up a window to show dags before the second dag combine pass
-view-edge-bundles - Pop up a window to show edge bundle graphs
-view-isel-dags - Pop up a window to show isel dags as they are selected
-view-legalize-dags - Pop up a window to show dags before legalize
-view-legalize-types-dags - Pop up a window to show dags before legalize types
-view-machine-block-freq-propagation-dags - Pop up a window to show a dag displaying how machine block frequencies propagate through the CFG.
=none - do not display graphs.
=fraction - display a graph using the fractional block frequency representation.
=integer - display a graph using the raw integer fractional block frequency representation.
-view-misched-cutoff=<uint> - Hide nodes with more predecessor/successor than cutoff
-view-misched-dags - Pop up a window to show MISched dags after they are processed
-view-sched-dags - Pop up a window to show sched dags as they are processed
-view-sunit-dags - Pop up a window to show SUnit dags after they are processed
が手元のclangは古くてHTMLに書かれたオプションが対応していない. 3.8でもだめ.
HEADのclangを取ってきてコンパイルして試す.
ついでにSelectionDAG.cppあたりのコードも眺めてみる.
試行錯誤して-view-misched-dagsの出力がz = x + xのnoaliasありなしが顕著に違った.
mischedはmachine instruction scheduling passというものらしい.
-help-list-hiddenを使うとそれを制御するオプションがたくさんでてくる.
-misched - Machine instruction scheduler to use
=default - Use the target's default scheduler choice.
=converge - Standard converging scheduler.
=ilpmax - Schedule bottom-up for max ILP
=ilpmin - Schedule bottom-up for min ILP
=shuffle - Shuffle machine instructions alternating directions
-misched-bottomup - Force bottom-up list scheduling
-misched-cluster - Enable memop clustering.
-misched-cutoff=<uint> - Stop scheduling after N instructions
-misched-cyclicpath - Enable cyclic critical path analysis.
-misched-dcpl - Print critical path length to stdout
-misched-fusion - Enable scheduling for macro fusion.
-misched-limit=<uint> - Limit ready list to N instructions
-misched-only-block=<uint> - Only schedule this MBB#
-misched-only-func=<string> - Only schedule this function
-misched-postra - Run MachineScheduler post regalloc (independent of preRA sched)
-misched-regpressure - Enable register pressure scheduling.
-misched-topdown - Force top-down list scheduling
まだ中身を理解してないけどいろいろ設定していたら-misched-topdownや-misched=ilpmaxなどで欲しいコードになることが分かった.
mov eax, dword ptr [esp + 16]
mov ecx, dword ptr [esp + 20]
mov edx, dword ptr [esp + 24]
mov edi, dword ptr [ecx + 4]
mov ebx, dword ptr [ecx + 8]
mov esi, dword ptr [ecx]
add esi, dword ptr [edx]
adc edi, dword ptr [edx + 4]
adc ebx, dword ptr [edx + 8]
mov dword ptr [eax + 8], ebx
mov dword ptr [eax + 4], edi
mov dword ptr [eax], esi
mov esi, dword ptr [edx + 12]
adc esi, dword ptr [ecx + 12]
mov dword ptr [eax + 12], esi
ただこれらの設定はいつもしてよいというわけではないようだ.
他の関数では逆にspillが増えることもあった(まだ詳細は見ていない).
あと肝心なarmでspillしっぱなしは変わらなかった. orz.
仕様が変わるといえばloadやgetelementptrなどの基本命令も3.6と3.8で違っていて焦った.
%x = load i64* %px ; 3.6で動くが3.8でエラー
%x = load i64, i64* %px ; 3.8で動くが3.6でエラー
さして意味のある差異にも見えないのに後方互換性も前方互換性もなく変えないでほしい.
あと_addcarry_u64がclangでエラーになるのはFixing PR26558: the adc builtins do not require the adx target attributeで直ってると@trairiaさんに教えていただいた.
ありがとうございます.
コンパイラがサポートする多倍長演算用intrinsic関数の現状
mclを32bitモードでコンパイルすると非常に遅かった理由はLLVMのaddのせいだった.
具体的には次のコードがひどいコードになってしまうせい.
define void @add1024(i1024* noalias %pz, i1024* noalias %px, i1024* noalias %py)
{
; version 3.8
%x = load i1024, i1024* %px
%y = load i1024, i1024* %py
; version 3.6
; %x = load i1024* %px
; %y = load i1024* %py
%z = add i1024 %x, %y
store i1024 %z, i1024* %pz
ret void
}
llcの出力を見ると
add1024:
push rbp
push r15
push r14
push r13
push r12
push rbx
mov rax, qword ptr [rsi + 120]
mov qword ptr [rsp - 8], rax # 8-byte Spill
mov r13, qword ptr [rsi + 112]
mov rax, qword ptr [rsi + 104]
mov qword ptr [rsp - 16], rax # 8-byte Spill
mov r12, qword ptr [rsi + 96]
mov rax, qword ptr [rsi + 88]
mov qword ptr [rsp - 24], rax # 8-byte Spill
mov rax, qword ptr [rsi + 80]
mov qword ptr [rsp - 32], rax # 8-byte Spill
mov r8, qword ptr [rsi + 72]
mov rcx, qword ptr [rsi + 64]
mov rax, qword ptr [rsi + 56]
mov r14, qword ptr [rdx + 16]
mov r15, qword ptr [rdx]
mov rbp, qword ptr [rdx + 8]
add r15, qword ptr [rsi]
adc rbp, qword ptr [rsi + 8]
adc r14, qword ptr [rsi + 16]
mov rbx, qword ptr [rsi + 48]
...
xとyの加算は下位bitからするのだからそれをそのままpzに書き込んでくれればいいのに1024bit分の加算が終わってからでないとpzに入れてくれない.
そのためレジスタ溢れが起こりstackに退避してloadし直している.
本当なら
mov rax, [rsi]
add rax, [rdi]
mov [rdx], rax
mov rax, [rsi + 8]
adc rax, [rdi + 8]
mov [rdx + 8], rax
mov rax, [rsi + 16]
adc rax, [rdi + 16]
mov [rdx + 16], rax
...
というコードになって欲しいのだが.
64bitで256bit加算のときはwork areaがregisterに取れていたので顕在しなかった.
32bit環境で1024bit加算をするとえらいことになる.
困った. 何かうまい方法ないでしょうか.
で, LLVMに頼らずコンパイラに任せられないかと調べ直してみた.
最近のコンパイラはcarry flagを扱うintrinsic関数を持っている.
現状一番優秀なのはVC. 2年以上前から_addcarry_u64が入っている.
この関数が理想系. 出力も完璧.
#include <stdint.h>
#include <intrin.h>
void add128(uint64_t *pz, const uint64_t *px, const uint64_t *py)
{
unsigned char c = 0;
c = _addcarry_u64(c, px[0], py[0], (unsigned long long*)&pz[0]);
_addcarry_u64(c, px[1], py[1], (unsigned long long*)&pz[1]);
}
//
add128:
mov rax, [rdx]
add rax, [r8]
mov [rcx], rax
mov rax, [rdx+8]
adc rax, [r8+8]
mov [rcx+8], rax
ret
ただclang-3.8でコンパイルするとコンパイラがエラーを吐く(何故?).
gcc-6.1ではしょぼいコードになる. あと多分ARM用だとこの関数は無い.
// gcc-6.1 -S -Ofast
add128:
movq (%rsi), %rax
addq (%rdx), %rax
setc %cl
movq %rax, (%rdi)
movq 8(%rdx), %rax
addb $-1, %cl
adcq 8(%rsi), %rax
movq %rax, 8(%rdi)
ret
しばらく前から導入された__builtin_uaddl_overflowはあくまでoverflowを検出するためのものでcarryの入力がないので使えない.
clangにはmultiprecision arithmetic builtinsというのがあるようだ.
#include <stdint.h>
void add192(uint64_t *pz, const uint64_t *px, const uint64_t *py)
{
unsigned long in = 0;
pz[0] = __builtin_addcl(px[0], py[0], in, &in);
pz[1] = __builtin_addcl(px[1], py[1], in, &in);
pz[2] = __builtin_addcl(px[2], py[2], in, &in);
}
残念ながらこちらもclang-3.8で
add192:
mov rax, qword ptr [rsi]
add rax, qword ptr [rdx]
sbb rcx, rcx
and ecx, 1
mov qword ptr [rdi], rax
mov rax, qword ptr [rsi + 8]
add rax, qword ptr [rdx + 8]
sbb r8b, r8b
add rax, rcx
sbb cl, cl
or cl, r8b
and cl, 1
movzx ecx, cl
mov qword ptr [rdi + 8], rax
mov rax, qword ptr [rdx + 16]
add rax, qword ptr [rsi + 16]
add rax, rcx
mov qword ptr [rdi + 16], rax
ret
という, わざわざ独自intrinsicを用意しといておまえは一体何がしたいんだ, というコードを生成する.
そもそもcarryにin, outがあるのが筋が悪い.
結局, 現状で多倍長計算のためのintrinsicでまともに使えるのはVCの_addcarry_u64だけというのは2年前から変わっていなかった.
やっぱり手書きアセンブラになってしまうのか. どうしよう.
昔
御意見は光成滋生<herumi@nifty.com>までお願いします