visual studio(c++モード)で、__int128を使おうとすると、
C4235: 非標準の拡張機能が使用されています: '__int128' キーワードはこのアーキテクチャではサポートされていません
と怒られます。
しかしながら、visual studio c++にはよく似た機能があるらしく、次のコードで使用できるようになります。
#if defined(_MSC_VER) && !defined(__clang__)
#include <__msvc_int128.hpp>
using u128 = std::_Unsigned128;
using i128 = std::_Signed128;
#else
using u128 = unsigned __int128;
using i128 = __int128;
#endif
しかしながら、参考文献によると、std::_Unsigned128はいつ使えなくなってもおかしくない機能のようです。
これをどこかに置くと、i128とu128型が利用可能になります。
また、iostreamで扱いたい場合は、独自のoperator<<を定義すればokです
(AIが書いたコードです。外部入力安全ではないかもしれません。)
static std::ostream& print_u128(std::ostream& os, u128 value) {
if (value == 0)
return os << '0';
int base = 10;
auto f = os.flags() & std::ios_base::basefield;
if (f == std::ios_base::hex) base = 16;
else if (f == std::ios_base::oct) base = 8;
std::string s;
while (value) {
unsigned digit = static_cast<unsigned>(value % base);
s.push_back(
digit < 10 ? '0' + digit
: (os.flags() & std::ios_base::uppercase ? 'A' : 'a') + (digit - 10)
);
value /= base;
}
std::reverse(s.begin(), s.end());
return os << s;
}
std::ostream& operator<<(std::ostream& os, u128 value) {
return print_u128(os, value);
}
std::ostream& operator<<(std::ostream& os, i128 value) {
if (value < 0) {
os << '-';
return print_u128(os, static_cast<u128>(-(value + 1)) + 1);
}
return print_u128(os, static_cast<u128>(value));
}
あとは煮たり焼いたりすればokです
int main() {
u128 m = 0x5d588b656c078965;
u128 a = 0x269ec3;
u128 base = 0x12345;
auto result = base * m + a;
std::cout << "u128 test: " << result << std::endl;
std::cout << "u128 test: " << std::hex << result << std::dec << std::endl;
i128 val1 = -100;
i128 val2 = 5;
auto result2 = val1 * val2;
std::cout << "i128 test: " << result2 << std::endl;
return 0;
}
i128 test: 501545016839495783052796
i128 test: 6a34cf51dc22af5c75fc
i128 test: -500
コンパイル結果
最適化を防ぐため、noop関数のヘッタを参照している。
#include <cstdint>
#include <iostream>
#include <cstdint>
#if defined(_MSC_VER) && !defined(__clang__)
#include <__msvc_int128.hpp>
using u128 = std::_Unsigned128;
using i128 = std::_Signed128;
#else
using u128 = unsigned __int128;
using i128 = __int128;
#endif
class data {
public:
static u128 noop();
};
int main() {
u128 m = data::noop();
u128 a = data::noop();
u128 base = data::noop();
auto result = base * m + a;
std::cout << static_cast<uint64_t>(result >> 64);
return 0;
}
msvc
/O2 では、std::_Unsigned128の演算が関数呼び出しではなく、64bit整数命令を組み合わせた多倍長演算として展開されていることがわかる。
# License: MSVC Proprietary
# The use of this compiler is only permitted for internal evaluation purposes and is otherwise governed by the MSVC License Agreement.
# See https://visualstudio.microsoft.com/license-terms/vs2022-ga-community/
base$ = 32
m$ = 48
a$ = 64
__$ArrayPad$ = 96
main PROC ; COMDAT
$LN89:
sub rsp, 120 ; 00000078H
mov rax, QWORD PTR __security_cookie
xor rax, rsp
mov QWORD PTR __$ArrayPad$[rsp], rax
lea rcx, QWORD PTR m$[rsp]
call static std::_Unsigned128 data::noop(void) ; data::noop
lea rcx, QWORD PTR a$[rsp]
call static std::_Unsigned128 data::noop(void) ; data::noop
lea rcx, QWORD PTR base$[rsp]
call static std::_Unsigned128 data::noop(void) ; data::noop
mov rax, QWORD PTR m$[rsp]
mul QWORD PTR base$[rsp]
add rax, QWORD PTR a$[rsp]
mov r8, rdx
mov rax, QWORD PTR base$[rsp+8]
mov rdx, QWORD PTR m$[rsp+8]
setb cl
imul rdx, QWORD PTR base$[rsp]
imul rax, QWORD PTR m$[rsp]
add rax, r8
add rdx, rax
add cl, -1
mov rcx, QWORD PTR __imp_std::basic_ostream<char,std::char_traits<char> > std::cout
adc rdx, QWORD PTR a$[rsp+8]
call QWORD PTR __imp_std::basic_ostream<char,std::char_traits<char> > & std::basic_ostream<char,std::char_traits<char> >::operator<<(unsigned __int64)
xor eax, eax
mov rcx, QWORD PTR __$ArrayPad$[rsp]
xor rcx, rsp
call __security_check_cookie
add rsp, 120 ; 00000078H
ret 0
main ENDP
O0では、ライブラリに頼っていることがわかる。
$LN3:
sub rsp, 168 ; 000000a8H
mov rax, QWORD PTR __security_cookie
xor rax, rsp
mov QWORD PTR __$ArrayPad$[rsp], rax
lea rcx, QWORD PTR m$[rsp]
call static std::_Unsigned128 data::noop(void) ; data::noop
lea rcx, QWORD PTR a$[rsp]
call static std::_Unsigned128 data::noop(void) ; data::noop
lea rcx, QWORD PTR base$[rsp]
call static std::_Unsigned128 data::noop(void) ; data::noop
npad 1
lea r8, QWORD PTR m$[rsp]
lea rdx, QWORD PTR base$[rsp]
lea rcx, QWORD PTR $T2[rsp]
call std::_Unsigned128 std::operator*(std::_Base128 const &,std::_Base128 const &) ; std::operator*
lea r8, QWORD PTR a$[rsp]
mov rdx, rax
lea rcx, QWORD PTR result$[rsp]
call std::_Unsigned128 std::operator+(std::_Base128 const &,std::_Base128 const &) ; std::operator+
npad 1
mov edx, 64 ; 00000040H
lea rcx, QWORD PTR $T1[rsp]
call std::_Base128::_Base128<int,0>(int) ; std::_Base128::_Base128<int,0>
lea r8, QWORD PTR $T1[rsp]
lea rdx, QWORD PTR result$[rsp]
lea rcx, QWORD PTR $T3[rsp]
call std::_Unsigned128 std::operator>>(std::_Unsigned128 const &,std::_Base128 const &) ; std::operator>>
mov rcx, rax
call std::_Base128::operator<unsigned __int64,0> unsigned __int64(void)const ; std::_Base128::operator<unsigned __int64,0> unsigned __int64
mov rdx, rax
mov rcx, QWORD PTR __imp_std::basic_ostream<char,std::char_traits<char> > std::cout
call QWORD PTR __imp_std::basic_ostream<char,std::char_traits<char> > & std::basic_ostream<char,std::char_traits<char> >::operator<<(unsigned __int64)
npad 1
xor eax, eax
mov rcx, QWORD PTR __$ArrayPad$[rsp]
xor rcx, rsp
call __security_check_cookie
add rsp, 168 ; 000000a8H
ret 0
main ENDP
GCC
unsigned __int128 は2つの64bitレジスタ(RDX:RAXなど)で保持され、演算は64bit命令を組み合わせた多倍長演算として展開されていることが分かる。
main:
push r13
push r12
push rbp
push rbx
sub rsp, 8
call data::noop()
mov rbx, rax
mov rbp, rdx
call data::noop()
mov r12, rax
mov r13, rdx
call data::noop()
mov edi, OFFSET FLAT:std::cout
imul rdx, rbx
mov rcx, rax
imul rbp, rax
mov rax, rbx
add rbp, rdx
mul rcx
add rbp, rdx
add rax, r12
mov rdx, rbp
adc rdx, r13
mov rsi, rdx
call std::ostream& std::ostream::_M_insert<unsigned long>(unsigned long)
add rsp, 8
xor eax, eax
pop rbx
pop rbp
pop r12
pop r13
ret
constexpr
コンパイル時定数化も対応しているようです。
#include <cstdint>
#include <iostream>
#if defined(_MSC_VER) && !defined(__clang__)
#include <__msvc_int128.hpp>
using u128 = std::_Unsigned128;
using i128 = std::_Signed128;
#else
using u128 = unsigned __int128;
using i128 = __int128;
#endif
constexpr u128 x = 100000000;
constexpr u128 y = x * 100000000000000000ull;
class data{
public:
static void test(u128 val);
};
int main() {
data::test(y);
return 0;
}
gcc
main:
sub rsp, 8
mov esi, 542101
movabs rdi, 1590897978359414784
call data::test(unsigned __int128)
xor eax, eax
add rsp, 8
ret
msvc
# License: MSVC Proprietary
# The use of this compiler is only permitted for internal evaluation purposes and is otherwise governed by the MSVC License Agreement.
# See https://visualstudio.microsoft.com/license-terms/vs2022-ga-community/
std::_Unsigned128 const y DQ 161401484a000000H ; y
DQ 0000000000084595H
$T1 = 32
main PROC ; COMDAT
$LN4:
sub rsp, 56 ; 00000038H
movaps xmm0, XMMWORD PTR std::_Unsigned128 const y
lea rcx, QWORD PTR $T1[rsp]
movdqa XMMWORD PTR $T1[rsp], xmm0
call static void data::test(std::_Unsigned128) ; data::test
xor eax, eax
add rsp, 56 ; 00000038H
ret 0
main ENDP
AVX2モードでの差
#include <cstdint>
#include <iostream>
#if defined(_MSC_VER) && !defined(__clang__)
#include <__msvc_int128.hpp>
using u128 = std::_Unsigned128;
using i128 = std::_Signed128;
#else
using u128 = unsigned __int128;
using i128 = __int128;
#endif
constexpr u128 x = 100000000;
constexpr u128 y = x * 100000000000000000ull;
class data{
public:
static void test(u128 val);
static u128 noop();
};
int main() {
auto z = y * data::noop() + data::noop();
data::test(z);
return 0;
}
gcc
-mavx2を使用してコンパイルした。ペクトル化は認められない。
main:
push rbp
push rbx
sub rsp, 8
call data::noop()
mov rbx, rax
mov rbp, rdx
call data::noop()
mov rdi, rdx
mov rsi, rax
movabs rdx, 1590897978359414784
imul rax, rbx, 542101
imul rbp, rdx
add rbp, rax
mov rax, rbx
mul rdx
add rbp, rdx
add rax, rsi
mov rdx, rbp
adc rdx, rdi
mov rdi, rax
mov rsi, rdx
call data::test(unsigned __int128)
add rsp, 8
xor eax, eax
pop rbx
pop rbp
ret
msvc
/arch:AVX2を使用してコンパイルした。
XMMレジスタや VEXエンコード命令が使用されるが、128bit整数演算そのものが SIMD化されるわけではない。
# License: MSVC Proprietary
# The use of this compiler is only permitted for internal evaluation purposes and is otherwise governed by the MSVC License Agreement.
# See https://visualstudio.microsoft.com/license-terms/vs2022-ga-community/
z$ = 32
$T2 = 32
$T3 = 32
$T4 = 48
$T5 = 64
__$ArrayPad$ = 80
main PROC ; COMDAT
$LN75:
mov QWORD PTR [rsp+8], rbx
push rdi
sub rsp, 96 ; 00000060H
mov rax, QWORD PTR __security_cookie
xor rax, rsp
mov QWORD PTR __$ArrayPad$[rsp], rax
lea rcx, QWORD PTR $T4[rsp]
call static std::_Unsigned128 data::noop(void) ; data::noop
mov r9, 1590897978359414784 ; 161401484a000000H
vpxor xmm0, xmm0, xmm0
vmovups XMMWORD PTR $T3[rsp], xmm0
mov r8, QWORD PTR [rax]
mov rcx, QWORD PTR [rax+8]
mov rax, r8
mul r9
imul rcx, r9
imul rbx, r8, 542101 ; 00084595H
add rcx, rdx
mov rdi, rax
add rbx, rcx
lea rcx, QWORD PTR $T5[rsp]
call static std::_Unsigned128 data::noop(void) ; data::noop
vpxor xmm0, xmm0, xmm0
vmovups XMMWORD PTR z$[rsp], xmm0
lea rcx, QWORD PTR $T2[rsp]
add rdi, QWORD PTR [rax]
mov QWORD PTR z$[rsp], rdi
adc rbx, QWORD PTR [rax+8]
mov QWORD PTR z$[rsp+8], rbx
vmovups xmm0, XMMWORD PTR z$[rsp]
vmovdqa XMMWORD PTR $T2[rsp], xmm0
call static void data::test(std::_Unsigned128) ; data::test
xor eax, eax
mov rcx, QWORD PTR __$ArrayPad$[rsp]
xor rcx, rsp
call __security_check_cookie
mov rbx, QWORD PTR [rsp+112]
add rsp, 96 ; 00000060H
pop rdi
ret 0
main ENDP