hexagon-clang 编译器下的 int 和 int32_t 不同导致的坑
1. 问题描述
原本在 Linux-x64, Windows, 各种嵌入式 arm linux 平台能编译通过的代码, 在 Hexagon SDK 5.5.0.1 (hexagon-clang 8.7.06) 上出现编译报错:
error: call to ‘saturate_cast’ is ambiguous
saturate_cast<uint8_t>(a);
^~~~~~~~~~~~~~~~~~~~~~
排查发现 hexagon-clang 编译器下, int
和 int32
并不是相同的类型, int
和 long
是相同类型, 并且 sizeof(long)=4
, 这和常见的 Linux 中是不一样的。
2. 最小复现代码
#include <stdint.h>
#include <stdio.h>
template<typename T> static inline T saturate_cast(uint32_t v) { return T(v); }
template<typename T> static inline T saturate_cast(int32_t v) { return T(v); }
int main()
{
int a = 233;
saturate_cast<uint8_t>(a);
return 0;
}
编译器版本:
root@2bfad214a508:~# /opt/Hexagon_SDK/5.5.0.1/tools/HEXAGON_Tools/8.7.06/Tools/bin/hexagon-clang++ --version
QuIC LLVM Hexagon Clang version 8.7.06
Target: hexagon
Thread model: posix
InstalledDir: /opt/Hexagon_SDK/5.5.0.1/tools/HEXAGON_Tools/8.7.06/Tools/bin
注意,这个版本 8.7.06 死后 QuIC LLVM 的版本, 查看 hexagon-clang 内置的宏,会发现它其实是 clang 15.0.0。
执行编译:
root@2bfad214a508:~# /opt/Hexagon_SDK/5.5.0.1/tools/HEXAGON_Tools/8.7.06/Tools/bin/hexagon-clang++ ./test5.cpp
./test5.cpp:10:5: error: call to 'saturate_cast' is ambiguous
saturate_cast<uint8_t>(a);
^~~~~~~~~~~~~~~~~~~~~~
./test5.cpp:4:38: note: candidate function [with T = unsigned char]
template<typename T> static inline T saturate_cast(uint32_t v) { return T(v); }
^
./test5.cpp:5:38: note: candidate function [with T = unsigned char]
template<typename T> static inline T saturate_cast(int32_t v) { return T(v); }
^
1 error generated.
在 Godbolt 上目前(2024-01-29 09:34:11)只有一个 hexagon-clang 编译器, 但是版本是 16.0.5, 无法发现上述问题: https://godbolt.org/z/nsb36rPcx
3. 编译期判断两个类型是否相同
C++11 提供了 std::is_same<T, U>
来判断类型 T 和 U 是否相同: https://en.cppreference.com/w/cpp/types/is_same 。
如果类型相同, 那么 value 属性为 true, 否则为 false。
std::is_same<T, U>::value
是编译期确定的。
3.1 运行期输出
也就是:
if (is_same<T, U>::value == true)
{
printf("T and U is same type\n");
}
else
{
printf("T and U is not same type\n");
}
上面这段代码的 printf 是运行期输出, 可以通过 hexagon-sim 这个模拟器运行输出:
root@2bfad214a508:~# /opt/Hexagon_SDK/5.5.0.1/tools/HEXAGON_Tools/8.7.06/Tools/bin/hexagon-sim ./a.out
hexagon-sim INFO: The rev_id used in the simulation is 0x00008d68 (v68n_1024)
int is not int32_t
Done!
T0: Insns=9562 Packets=4713
T1: Insns=0 Packets=0
T2: Insns=0 Packets=0
T3: Insns=0 Packets=0
T4: Insns=0 Packets=0
T5: Insns=0 Packets=0
Total: Insns=9562 Pcycles=14142
3.2 编译期输出
C++11 提供了 static_assert
在编译期执行 assert 和打印: https://en.cppreference.com/w/cpp/language/static_assert
static_assert( bool-constexpr , message ) (since C++11)
If bool-constexpr is well-formed and evaluates to true, or is evaluated in the context of a template definition and the template is uninstantiated, this declaration has no effect. Otherwise a compile-time error is issued, and the text of message, if any, is included in the diagnostic message.
如果 static_assert()
里的条件表达式结果为 true, 则什么都不输出; 如果条件为 false, 则输出 message
变量的内容。
因此写下如下的编译期代码:
#include <stdint.h>
#include <stdio.h>
#include <type_traits>
template<typename T> static inline T saturate_cast(uint32_t v) { return T(v); }
template<typename T> static inline T saturate_cast(int32_t v) { return T(v); }
int main()
{
//int a = 233;
//saturate_cast<uint8_t>(a);
static_assert(std::is_same<int, int32_t>::value, "int is not int32_t");
static_assert(std::is_same<int, long>::value, "int is not long");
static_assert(!std::is_same<int32_t, long>::value, "int32_t is same as long");
return 0;
}
编译结果:
root@2bfad214a508:~# /opt/Hexagon_SDK/5.5.0.1/tools/HEXAGON_Tools/8.7.06/Tools/bin/hexagon-clang++ ./test5.cpp
./test5.cpp:13:5: error: static assertion failed due to requirement 'std::is_same<int, long>::value': int is not int32_t
static_assert(std::is_same<int, int32_t>::value, "int is not int32_t");
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./test5.cpp:14:5: error: static assertion failed due to requirement 'std::is_same<int, long>::value': int is not long
static_assert(std::is_same<int, long>::value, "int is not long");
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./test5.cpp:15:5: error: static assertion failed due to requirement '!std::is_same<long, long>::value': int32_t is same as long
static_assert(!std::is_same<int32_t, long>::value, "int32 is same as long");
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 errors generated.
而如果是 Linux-x64 上的 GCC11, 编译结果则是:
test5.cpp: In function ‘int main()’:
test5.cpp:14:44: error: static assertion failed: int is not long
14 | static_assert(std::is_same<int, long>::value, "int is not long");
| ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
3.4 编译期查看 sizeof(long)
#include <stdint.h>
#include <stdio.h>
#include <type_traits>
template<typename T> static inline T saturate_cast(uint32_t v) { return T(v); }
template<typename T> static inline T saturate_cast(int32_t v) { return T(v); }
#include <cstddef>
template <std::size_t x>
struct show_size;
void foo()
{
show_size<sizeof(long)>();
show_size<sizeof(int*)>();
}
int main()
{
//int a = 233;
//saturate_cast<uint8_t>(a);
// static_assert(std::is_same<int, int32_t>::value, "int is not int32_t");
// static_assert(std::is_same<int, long>::value, "int is not long");
// static_assert(!std::is_same<int32_t, long>::value, "int32 is same as long");
return 0;
}
编译结果:
root@2bfad214a508:~# /opt/Hexagon_SDK/5.5.0.1/tools/HEXAGON_Tools/8.7.06/Tools/bin/hexagon-clang++ ./test5.cpp
./test5.cpp:14:5: error: implicit instantiation of undefined template 'show_size<4>'
show_size<sizeof(long)>();
^
./test5.cpp:10:8: note: template is declared here
struct show_size;
^
./test5.cpp:15:5: error: implicit instantiation of undefined template 'show_size<4>'
show_size<sizeof(int*)>();
^
./test5.cpp:10:8: note: template is declared here
struct show_size;
^
2 errors generated.
说明 sizeof(long) 在 hexagon-clang 编译器下等于4, 指针类型在 hexagon-clang 编译器下也等于 4 字节, 是32位的编译器。
3.5 hexagon 上的类型的结论
int 和 int32 不是同一个类型。
int 和 long 也不是同一个类型。
int32_t 和 long 是同一个类型。
hexagon-clang 是32位而不是64位的指令集架构输出。
4. 编译期给 hexagon-clang 打补丁
对于现有代码, 要在 hexagon-clang 平台编译通过, 需要针对 hexagon-clang 编译器打补丁。
4.1 通过 __hexagon__
宏判断
查看 hexagon-clang 编译器默认提供的宏定义:
root@2bfad214a508:~# /opt/Hexagon_SDK/5.5.0.1/tools/HEXAGON_Tools/8.7.06/Tools/bin/hexagon-clang++ -dM -E -x c++ - < /dev/null | grep -i 'hexagon'
#define __HEXAGON_ARCH__ 68
#define __HEXAGON_PHYSICAL_SLOTS__ 4
#define __HEXAGON_V68__ 1
#define __VERSION__ "Clang 15.0.0 (/local/mnt/workspace/bots/llvm-release-bot-sles12_lv_5/hexagon-clang-87/build/llvm-top/clang 011f725a1454ed0d47d2ccfbb9097eb99743bf21)"
#define __clang_version__ "15.0.0 (/local/mnt/workspace/bots/llvm-release-bot-sles12_lv_5/hexagon-clang-87/build/llvm-top/clang 011f725a1454ed0d47d2ccfbb9097eb99743bf21)"
#define __hexagon__ 1
因此使用 __hexagon__
来做判断:
#include <stdint.h>
#include <stdio.h>
template<typename T> static inline T saturate_cast(uint32_t v) { return T(v); }
template<typename T> static inline T saturate_cast(int32_t v) { return T(v); }
#if __hexagon__
template<typename T> static inline T saturate_cast(int v) { return T(v); }
#endif
int main()
{
int a = 233;
saturate_cast<uint8_t>(a);
return 0;
}
4.2 使用 enable_if
执行条件编译
C++11 提供了 enable_if
: https://en.cppreference.com/w/cpp/types/enable_if
template< bool B, class T = void >
struct enable_if;
If B is true, std::enable_if has a public member typedef type, equal to T; otherwise, there is no member typedef.
如果 B
是 true, 那么 std::enable_if
会有一个公共成员 type
, 它等于 T
;如果 B 为 false, 则没有这个成员。
因此当编译期 B 为 true 就可以选则 T 类型, 进而定义函数; 当编译期 B 为 false, 就不会定义函数:
#include <stdint.h>
#include <stdio.h>
#include <type_traits>
template<typename T> static inline T saturate_cast(uint32_t v) { return T(v); }
template<typename T> static inline T saturate_cast(int32_t v) { return T(v); }
// #if __hexagon__
// template<typename T> static inline T saturate_cast(int v) { return T(v); }
// #endif
template<typename T> static inline
typename std::enable_if<!std::is_same<int, int32_t>::value, T>::type saturate_cast(int v)
{
return T(v);
}
int main()
{
int a = 233;
saturate_cast<uint8_t>(a);
return 0;
}
解释: 在编译期, 如果 int 和 int32_t 类型不同, 那么让编译器得到类型 T, 进而定义了如下函数:
template<typename T> static inline
typename T saturate_cast(int v)
{
return T(v);
}
而如果 int 和 int32_t 类型相同, 那么就不会执行这个函数的编译了。
5. 类型提升导致的新坑
当两个 unsigned char
相加, 结果类型会是 int 类型, 这是类型提升。 此时在 hexagon-clang 下会再次触发模板函数如 saturate_cast
的匹配失败(假设没有做前面增加的 “补丁”):
#include <stdint.h>
#include <stdio.h>
#include <type_traits>
int main()
{
unsigned char a = 3;
unsigned char b = 4;
auto c = a + b;
static_assert(std::is_same<decltype(c), int>::value, "c is not int");
static_assert(std::is_same<decltype(c), int32_t>::value, "c is not int32_t");
return 0;
}
编译输出:
root@2bfad214a508:~# /opt/Hexagon_SDK/5.5.0.1/tools/HEXAGON_Tools/8.7.06/Tools/bin/hexagon-clang++ ./test5.cpp
./test5.cpp:41:5: error: static assertion failed due to requirement 'std::is_same<int, long>::value': c is not int32_t
static_assert(std::is_same<decltype(c), int32_t>::value, "c is not int32_t");
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
其中报错是说 c 的类型是 int, 但不是 int32_t.
也尝试了使用 uint8_t a=3
, uint8_t b=4
, 但是结果是一样的。
因此, 不仅仅是 saturate_cast
模板函数本身要对 int 和 int32_t 同时做处理, 对于其他模板函数也有同样的问题。
6. 结论
总结一下 C++ 方面的技巧:
std::is_same<T, U>::value
是个好东西,编译期的static_assert
也是个好东西,编译期的std::enable_if
是做条件编译,也挺好用的
总结一下 hexagon-clang 的坑:
- int 和 int32_t 不是同一个类型
- 遇到 “ambibuous” 的报错,需要检查模板相关的类型报错