
C 컴파일러는 빌드 대상이 지원하는 명령어 집합 아키텍처(ISA) 기능에 따라 전처리기 매크로를 정의한다. 이를 이용하면 CRC-32C처럼 특정 기능에 맞는 구현을 선택하거나, 지원하지 않는 대상에서 컴파일을 중단할 수 있다. 이는 실행 중 CPU를 탐지하는 방식이 아니라 컴파일 대상의 속성을 확인하는 방법이다.
실행 중 탐지와 달리, 컴파일 시점 확인은 -march로 지정했거나 -march=native를 사용해 컴파일러가 감지한 대상을 기준으로 한다. x86에서 실행 중 CPU 기능은 CPUID 명령어를 사용하며, GCC와 Clang은 __builtin_cpu_supports, MSVC는 __check_isa_support를 제공한다. Arm에서는 명령어 집합 속성 레지스터(ISAR)와 다른 레지스터에 CPU 기능이 기술된다. Linux는 getauxval(AT_HWCAP), ARM64 ELF hwcaps 또는 레지스터 에뮬레이션을 통해 정보를 노출한다.
함수에 ISA별 대상 속성(GCC AArch64 함수 속성 참고)을 붙여 서로 다른 기능 집합에 맞게 컴파일할 수도 있다. 다만 이 방식도 구현 사이의 실행 중 분기(dispatch)가 필요하며, 여기서 다루는 컴파일 시점 판별과는 별개다.
CRC-32C는 현대 CPU에서 흔히 지원되는 기능이다. Arm에서는 v8.0에서 선택 사항이고 v8.1부터 필수이며, __ARM_FEATURE_CRC32 매크로로 표시된다. x86-64에서는 SSE4.2에 포함되고 __SSE4_2__로 표시된다. 아래 GCC·Clang 예시는 대상 아키텍처에 맞는 8바이트 입력 CRC32-C 내장 함수를 선택하고, 지원되는 구현이 없으면 컴파일을 중단한다. CRC에 대한 설명은 Cyclic redundancy check, Arm 명령어 조건은 Arm A-profile A64 명령어 집합 문서에서 확인할 수 있다.
#if defined(__x86_64__) && defined(__SSE4_2__)
#include <nmmintrin.h>
static inline uint32_t crc32c_u64(uint32_t crc, uint64_t v)
{
return (uint32_t) _mm_crc32_u64(crc, v);
}
#elif defined(__ARM_FEATURE_CRC32)
#include <arm_acle.h>
static inline uint32_t crc32c_u64(uint32_t crc, uint64_t v)
{
return __crc32cd(crc, v);
}
#else
#error "No CRC32C implementation for the target architecture"
// This is where the fallback software implementation would be defined.
#endif
Arm 기능 매크로는 Arm C Language Extensions(ACLE)에 규정돼 있지만, x86-64 매크로는 관례에 따른다. 매크로 목록은 아키텍처와 컴파일러에 따라 다르며 플랫폼이나 컴파일러 버전별로 달라질 수 있다. 예를 들어 MSVC는 __x86_64__ 대신 _M_X64를 사용하고 SSE4.2 매크로를 정의하지 않는다. 대신 컴파일 시 평가되는 __check_arch_support(__IA_SUPPORT_SSE42, 0) 내장 함수를 사용해야 한다. MSVC의 매크로 목록은 전처리기 사전 정의 매크로 문서에서 볼 수 있다.
GCC와 Clang에서는 -E -dM으로 전처리기 매크로를 출력해 대상별 정의를 확인할 수 있다. 다음은 Red Hat의 GCC 16.2.1-2와 Fedora의 Clang 22.1.8-4.fc44에서 Armv8.1-A 대상을 지정한 예다.
$ gcc -march=armv8.1-a -E -dM - </dev/null | grep -E 'ARM_(ARCH|FEATURE_CRC)'
#define __ARM_ARCH_PROFILE 65
#define __ARM_ARCH 8
#define __ARM_ARCH_8A 1
#define __ARM_FEATURE_CRC32 1
#define __ARM_ARCH_ISA_A64 1
$ clang -march=armv8.1-a -E -dM - </dev/null | grep -E 'ARM_(ARCH|FEATURE_CRC)'
#define __ARM_ARCH 8
#define __ARM_ARCH_ISA_A64 1
#define __ARM_ARCH_PROFILE 'A'
#define __ARM_FEATURE_CRC32 1
MSVC에서는 /PD로 매크로 정의를 출력할 수 있다. 예시의 두 컴파일러는 ACLE가 Armv8.1-A에 요구하는 801과 다른 값을 출력한다. 따라서 실제 빌드 설정에서 매크로를 확인하고, 아키텍처 버전 매크로보다 기능별 매크로를 사용하는 편이 낫다.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요