다음을 통해 공유


ARM64EC ABI 규칙 개요

ARM64EC는 ARM64 이진이 기본적으로 x64 코드와 상호 운용 가능하게 실행될 수 있도록 하는 ABI(애플리케이션 이진 인터페이스)입니다. 특히 ARM64EC ABI는 호출 규칙, 스택 사용, 데이터 맞춤 등 x64 소프트웨어 규칙을 따르므로 ARM64EC와 x64 코드가 상호 운용 가능합니다. 운영 체제는 이진 코드의 x64 부분을 에뮬레이트합니다. (ARM64EC의 EC는 에뮬레이션 호환 가능을 나타냅니다.)

x64 및 ARM64 ABI에 대한 자세한 내용은 x64 ABI 규칙 개요ARM64 ABI 규칙 개요를 참조하세요.

ARM64EC는 x64와 ARM 기반 아키텍처 간의 메모리 모델 차이를 해결하지 않습니다. 자세한 내용은 일반적인 Microsoft C++ ARM 마이그레이션 문제를 참조하세요.

정의

  • ARM64 - 기존 ARM64 코드가 포함된 ARM64 프로세스용 코드 스트림입니다.
  • ARM64EC - x64 코드와의 상호 운용성을 제공하기 위해 ARM64 레지스터 집합의 하위 집합을 활용하는 코드 스트림입니다.

레지스터 매핑

x64 프로세스에는 ARM64EC 코드를 실행하는 스레드가 있을 수 있습니다. 따라서 항상 x64 레지스터 컨텍스트를 검색할 수 있으며, ARM64EC는 에뮬레이트된 x64 레지스터에 1:1로 매핑되는 ARM64 코어 레지스터의 하위 집합을 사용합니다. ARM64EC가 x18에서 TEB(스레드 환경 블록) 주소를 읽는 경우를 제외하고는 이 하위 집합 외부의 레지스터를 사용하지 않아야 합니다.

네이티브 ARM64 프로세스는 일부 또는 다수의 함수가 ARM64EC로 다시 컴파일될 때 성능이 저하되어서는 안 됩니다. 성능을 유지하기 위해 ABI는 다음 원칙을 따릅니다.

  • ARM64EC 레지스터 하위 집합에는 ARM64 함수 호출 규칙의 일부인 모든 레지스터가 포함됩니다.

  • ARM64EC 호출 규칙은 ARM64 호출 규칙에 직접 매핑됩니다.

__chkstk_arm64ec와 같은 특수 도우미 루틴은 사용자 지정 호출 규칙 및 레지스터를 사용합니다. 이러한 레지스터는 레지스터의 ARM64EC 하위 집합에도 포함됩니다.

정수 레지스터에 대한 레지스터 매핑

ARM64EC 레지스터 x64 레지스터 ARM64EC 호출 규칙 ARM64 호출 규칙 x64 호출 규칙
x0 rcx 변덕스러운 변덕스러운 변덕스러운
x1 rdx 변덕스러운 변덕스러운 변덕스러운
x2 r8 변덕스러운 변덕스러운 변덕스러운
x3 r9 변덕스러운 변덕스러운 변덕스러운
x4 r10 변덕스러운 변덕스러운 변덕스러운
x5 r11 변덕스러운 변덕스러운 변덕스러운
x6 mm1(x87 R1 레지스터의 하위 64비트) 변덕스러운 변덕스러운 변덕스러운
x7 mm2(x87 R2 레지스터의 하위 64비트) 변덕스러운 변덕스러운 변덕스러운
x8 rax 변덕스러운 변덕스러운 변덕스러운
x9 mm3(x87 R3 레지스터의 하위 64비트) 변덕스러운 변덕스러운 변덕스러운
x10 mm4(x87 R4 레지스터의 하위 64비트) 변덕스러운 변덕스러운 변덕스러운
x11 mm5(x87 R5 레지스터의 하위 64비트) 변덕스러운 변덕스러운 변덕스러운
x12 mm6(x87 R6 레지스터의 하위 64비트) 변덕스러운 변덕스러운 변덕스러운
x13 해당 없음 허용 안 함 변덕스러운 해당 없음
x14 해당 없음 허용 안 함 변덕스러운 해당 없음
x15 mm7(x87 R7 레지스터의 하위 64비트) 변덕스러운 변덕스러운 변덕스러운
x16 각 x87 R0-R3 레지스터의 상위 16비트 휘발성(xip0) 휘발성(xip0) 변덕스러운
x17 각 x87 R4-R7 레지스터의 상위 16비트 휘발성(xip1) 휘발성(xip1) 변덕스러운
x18 GS.base 고정(TEB) 고정(TEB) 고정(TEB)
x19 r12 비휘발성 비휘발성 비휘발성
x20 r13 비휘발성 비휘발성 비휘발성
x21 r14 비휘발성 비휘발성 비휘발성
x22 r15 비휘발성 비휘발성 비휘발성
x23 해당 없음 허용 안 함 비휘발성 해당 없음
x24 해당 없음 허용 안 함 비휘발성 해당 없음
x25 rsi 비휘발성 비휘발성 비휘발성
x26 rdi 비휘발성 비휘발성 비휘발성
x27 rbx 비휘발성 비휘발성 비휘발성
x28 해당 없음 허용 안 함 허용 안 함 해당 없음
fp rbp 비휘발성 비휘발성 비휘발성
lr mm0(x87 R0 레지스터의 하위 64비트) 둘 다 둘 다 둘 다
sp rsp 비휘발성 비휘발성 비휘발성
pc rip 명령 포인터 명령 포인터 명령 포인터
PSTATE 하위 집합: N/Z/C/V/SS1, 2 RFLAGS 하위 집합: SF/ZF/CF/OF/TF 변덕스러운 변덕스러운 변덕스러운
해당 없음 RFLAGS 하위 집합: PF/AF 해당 없음 해당 없음 변덕스러운
해당 없음 RFLAGS 하위 집합: DF 해당 없음 해당 없음 비휘발성

1PSTATERFLAGS 사이의 매핑을 직접 읽거나 쓰거나 계산하는 것을 방지합니다. 이 비트는 향후 사용될 수 있으며 변경될 수 있습니다.

2 ARM64EC 캐리 플래그 C는 빼기 연산을 위해 x64 캐리 플래그 CF의 역으로 사용됩니다. 플래그는 ARM64EC 및 x64 함수 간에 전환할 때 휘발성으로 폐기되므로 특별한 처리가 필요하지 않습니다.

벡터 레지스터에 대한 매핑 등록

ARM64EC 레지스터 x64 레지스터 ARM64EC 호출 규칙 ARM64 호출 규칙 x64 호출 규칙
v0-v5 xmm0-xmm5 변덕스러운 변덕스러운 변덕스러운
v6-v7 xmm6-xmm7 변덕스러운 변덕스러운 비휘발성
v8-v15 xmm8-xmm15 휘발성 1 휘발성 1 비휘발성
v16-v31 xmm16-xmm31 허용 안 함 변덕스러운 허용되지 않음(x64 에뮬레이터는 AVX-512를 지원하지 않음)
FPCR 2 MXCSR[15:6] 비휘발성 비휘발성 비휘발성
FPSR 2 MXCSR[5:0] 변덕스러운 변덕스러운 변덕스러운

1 이러한 ARM64 레지스터는 하위 64비트가 비휘발성이지만 상위 64비트는 휘발성이라는 점에서 특별합니다. x64 호출자의 관점에서 보면 호출 수신자가 데이터를 폐기하므로 사실상 휘발성입니다.

2FPCRFPSR의 매핑을 직접 읽거나 쓰거나 계산하지 마세요. 이 비트는 향후 사용될 수 있으며 변경될 수 있습니다.

구조체 패킹

ARM64EC는 ARM64EC 코드와 x64 코드 간의 interop을 보장하기 위해 x64에 사용되는 것과 동일한 구조체 패킹 규칙을 따릅니다. x64 구조체 패킹에 대한 자세한 내용과 예는 x64 ABI 규칙 개요를 참조하세요.

부동 소수점 예외

ARM CPU가 예외를 지원하는지 확인하려면 예외를 사용하도록 설정하는 값을 FPCR 레지스터에 쓴 다음 다시 읽습니다. CPU가 부동 소수점 예외를 지원하는 경우 지원되는 예외에 해당하는 비트는 설정된 상태로 유지되고 CPU는 지원되지 않는 예외에 대한 비트를 다시 설정합니다.

ARM64EC에서, Windows는 프로세서의 부동 소수점 예외를 감지하고 FPCR 레지스터에서 비활성화합니다. 이렇게 하면 다양한 프로세서 변형에서 일관된 동작이 보장됩니다.

에뮬레이션 도우미 ABI 루틴

ARM64EC 코드와 썽크는 에뮬레이션 도우미 루틴을 사용하여 x64와 ARM64EC 함수 간을 전환합니다.

다음 표에서는 각 특수 ABI 루틴과 ABI가 사용하는 레지스터에 대해 설명합니다. 루틴은 ABI 열 아래에 나열된 보존 레지스터를 수정하지 않습니다. 목록에 없는 레지스터에 대해서는 어떤 가정도 해서는 안 됩니다. 디스크에서 ABI 루틴 포인터는 null입니다. 로드 시 로더는 x64 에뮬레이터 루틴을 가리키도록 포인터를 업데이트합니다.

이름 설명 ABI
__os_arm64x_dispatch_call_no_redirect x64 대상(x64 함수 또는 x64 빠른 전달 시퀀스)을 호출하기 위해 종료 썽크에 의해 호출됩니다. 루틴은 ARM64EC 반환 주소(LR 레지스터에 있음)와 x64 에뮬레이터를 호출하는 blr x16 명령 뒤에 오는 명령의 주소를 푸시합니다. 그런 다음 blr x16 명령을 실행합니다. x8(rax)의 반환 값
__os_arm64x_dispatch_ret x64 호출자에게 반환 위해 입력 썽크에 의해 호출됩니다. 스택에서 x64 반환 주소를 표시하고 x64 에뮬레이터를 호출하여 해당 주소로 이동합니다. 해당 없음
__os_arm64x_check_call ARM64EC 코드에 의해 호출되며, 종료 덩크에 대한 포인터와 실행할 간접 ARM64EC 대상 주소를 사용합니다. ARM64EC 대상은 패치 가능한 것으로 간주되며 실행은 항상 호출된 것과 동일한 데이터 또는 수정된 데이터를 사용하여 호출자에게 반환됩니다. 인수:
x9: 대상 주소
x10: 종료 thunk 주소
x11: 빠른 전달 시퀀스 주소

출력:
x9: 대상 함수가 우회된 경우 빠른 전달 시퀀스의 주소를 포함합니다.
x10: 종료 함수 랩 주소
x11: 함수가 우회된 경우 종료 썽크 주소가 포함됩니다. 그렇지 않으면 대상 주소가 다음으로 점프됨

보존된 레지스터: x0-x8, x15(chkstk). 및 q0-q7
__os_arm64x_check_icall ARM64EC 코드에 의해 호출되어 x64 또는 ARM64EC 대상 주소로 점프를 처리할 수 있도록 종료 썽크에 대한 포인터와 함께 제공됩니다. 대상이 x64이고 x64 코드가 패치되지 않은 경우 루틴은 대상 주소 레지스터를 설정합니다. 존재하는 경우 함수의 ARM64EC 버전을 가리킵니다. 그렇지 않으면 x64 대상에 전환하는 종료 썽크를 가리키도록 레지스터를 설정합니다. 그런 다음 호출하는 ARM64EC 코드로 돌아가서 레지스터의 주소로 점프합니다. 이 루틴은 __os_arm64x_check_call의 최적화되지 않은 버전으로, 대상 주소가 컴파일 시간에 알려지지 않습니다.

간접 호출의 호출 지점에서 사용됨
인수:
x9: 대상 주소
x10: 종료 섕크 주소
x11: 빠른 전달 시퀀스 주소

출력:
x9: 대상 함수가 우회된 경우 빠른 전달 시퀀스의 주소를 포함합니다.
x10: 종료 펑크 주소
x11: 함수가 우회된 경우 종료 썽크 주소를 포함합니다. 그렇지 않으면 대상 주소가 다음으로 점프됨

보존된 레지스터: x0-x8, x15(chkstk) 및 q0-q7
__os_arm64x_check_icall_cfg __os_arm64x_check_icall과 동일하지만 지정된 주소가 유효한 제어 흐름 Graph 간접 호출 대상인지 확인 인수:
x10: 종료 썽크의 주소
x11: 대상 함수의 주소

외부:
x9: 대상이 x64인 경우 함수에 대한 주소입니다. 그 이외의 경우에는 정의되지 않음
x10: 종료 thunk의 주소
x11: 대상이 x64인 경우 종료 썽크의 주소가 포함됩니다. 그렇지 않으면 함수의 주소를 사용합니다.

보존된 레지스터: x0-x8, x15(chkstk) 및 q0-q7
__os_arm64x_get_x64_information 라이브 x64 레지스터 컨텍스트의 요청된 부분을 가져옵니다. _Function_class_(ARM64X_GET_X64_INFORMATION) NTSTATUS LdrpGetX64Information(_In_ ULONG Type, _Out_ PVOID Output, _In_ PVOID ExtraInfo)
__os_arm64x_set_x64_information 라이브 x64 레지스터 컨텍스트의 요청된 부분을 설정합니다. _Function_class_(ARM64X_SET_X64_INFORMATION) NTSTATUS LdrpSetX64Information(_In_ ULONG Type,_In_ PVOID Input, _In_ PVOID ExtraInfo)
__os_arm64x_x64_jump 서명이 없는 조정자 및 서명을 가질 수 있는 다른 함수에 대한 호출을 직접 전달(jmp)하는 기타 썽크에 사용되며, 실제 대상에 대한 올바른 썽크의 잠재적 적용을 연기합니다. 인수:
x9: 점프할 대상

모든 매개 변수 레지스터가 보존됨(전달됨)

Thunk (썽크)

Thunk는 ARM64EC와 x64 함수가 서로 호출할 수 있도록 지원하는 하위 수준의 메커니즘입니다. ARM64EC 함수 입력을 위한 입력 썽크와 x64 함수 호출을 위한 종료 썽크의 두 가지 형식이 있습니다.

입력 썽크 및 내장 입력 썽크: x64에서 ARM64EC로 함수 호출

C/C++ 함수가 ARM64EC로 컴파일될 때 x64 호출자를 지원하기 위해 툴체인은 ARM64EC 컴퓨터어 코드로 구성된 단일 입력 썽크를 생성합니다. 내장 함수에는 자체 Entry Thunk가 있습니다. 다른 모든 함수는 호출 규칙, 매개 변수 및 반환 형식이 일치하는 모든 함수와 입력 썽크를 공유합니다. 썽크의 콘텐츠는 C/C++ 함수의 호출 규칙에 따라 달라집니다.

매개 변수 및 반환 주소를 처리하는 것 외에도 썽크는 ARM64EC 벡터 레지스터 매핑으로 인해 발생하는 ARM64EC와 x64 벡터 레지스터 간의 변동성 차이를 연결합니다.

ARM64EC 레지스터 x64 레지스터 ARM64EC 호출 규칙 ARM64 호출 규칙 x64 호출 규칙
v6-v15 xmm6-xmm15 휘발성이지만 엔트리 벙크에 저장/복원됩니다(x64에서 ARM64EC로). 휘발성 또는 부분 휘발성인 상위 64비트 비휘발성

입력 썽크는 다음 작업을 수행합니다.

매개 변수 번호 스택 사용
0-4 ARM64EC v6v7을 호출자 할당 홈 공간에 저장합니다.

호출 수신자는 홈 공간 개념이 없는 ARM64EC이므로 저장된 값이 손상되지 않습니다.

스택에 추가 128바이트를 할당하고 ARM64EC v8부터 v15까지 저장합니다.
5-8 x4 = 스택의 5번째 매개 변수
x5 = 스택의 6번째 매개 변수
x6 = 스택의 7번째 매개 변수
x7 = 스택의 8번째 매개 변수

매개 변수가 SIMD인 경우 v4-v7 레지스터가 대신 사용됩니다.
9+ 스택에 AlignUp(NumParams - 8 , 2) * 8바이트를 할당합니다. *

9번째 및 나머지 매개 변수를 이 영역에 복사합니다.

* 값을 짝수로 정렬하면 스택이 16바이트로 정렬된 상태로 유지됩니다.

함수가 32비트 정수 매개 변수를 허용하는 경우 썽크는 부모 레지스터의 전체 64비트 대신 32비트만 푸시하도록 허용됩니다.

다음으로 썽크는 ARM64 bl 명령을 사용하여 ARM64EC 함수를 호출합니다. 함수가 반환된 후, 썽크는 다음과 같습니다.

  1. 스택 할당을 취소합니다.
  2. __os_arm64x_dispatch_ret 에뮬레이터 도우미를 호출하여 x64 반환 주소를 표시하고 x64 에뮬레이션을 다시 시작합니다.

종료 함수: ARM64EC에서 x64로 함수 호출

ARM64EC C/C++ 함수가 잠재적 x64 코드에 대해 수행하는 모든 호출에 대해 MSVC 툴체인은 종료 썽크를 생성합니다. 썽크의 콘텐츠는 x64 호출 수신자의 매개 변수와 호출 수신자가 표준 호출 규칙 또는 __vectorcall을 사용하는지 여부에 따라 달라집니다. 컴파일러는 호출 수신자에 대한 함수 선언에서 이 정보를 가져옵니다.

먼저 'thunk'는 ARM64EC lr 레지스터에 있는 반환 주소와 스택을 16바이트로 정렬하기 위해 더미 8바이트 값을 푸시합니다. 둘째, 섕크는 매개변수를 처리합니다.

매개 변수 번호 스택 사용
0-4 스택에 32바이트의 홈 공간을 할당합니다.
5-8 스택에서 더 높은 위치에 AlignUp(NumParams - 4, 2) * 8바이트를 더 할당합니다. *

ARM64EC의 x4-x7에서 5번째 및 후속 매개 변수를 이 추가 공간에 복사합니다.
9+ 9번째 및 나머지 매개 변수를 추가 공간에 복사합니다.

* 값을 짝수로 정렬하면 스택이 16바이트로 정렬된 상태로 유지됩니다.

셋째, 썽크(thunk)는 x64 에뮬레이터를 호출해 x64 함수를 실행하도록 __os_arm64x_dispatch_call_no_redirect 에뮬레이터 도우미를 호출합니다. 호출은 blr x16 명령이어야 합니다(편의상 x16은 휘발성 레지스터임). x64 에뮬레이터가 이 명령을 힌트로 구문 분석하므로 blr x16 명령이 필요합니다.

x64 함수는 일반적으로 x64 ret 명령을 사용하여 에뮬레이터 도우미로 돌아가려고 합니다. 이 시점에서 x64 에뮬레이터는 ARM64EC 코드에 있음을 검색합니다. 그런 다음 ARM64 blr x16 명령인 이전 4바이트 힌트를 읽습니다. 이 힌트는 반환 주소가 이 도우미에 있음을 나타내므로 에뮬레이터는 이 주소로 직접 이동합니다.

x64 함수는 x64 jmpcall을 포함한 모든 분기 명령을 사용하여 에뮬레이터 도우미로 돌아갈 수 있습니다. 에뮬레이터는 이러한 시나리오도 처리합니다.

그런 다음 도우미가 thunk로 돌아오면, thunk는 다음을 수행합니다.

  1. 스택 할당을 취소합니다.
  2. ARM64EC lr 레지스터를 표시합니다.
  3. ARM64 ret lr 명령을 실행합니다.

ARM64EC 함수 이름 장식

ARM64EC 함수 이름에는 언어별 장식 뒤에 보조 장식이 적용됩니다. C 링크가 있는 함수(C로 컴파일되거나 extern "C"를 사용하여 컴파일됨)의 경우 이름 앞에 #가 추가됩니다. C++ 데코레이팅된 함수의 경우 $$h 태그가 이름에 삽입됩니다.

foo         => #foo
?foo@@YAHXZ => ?foo@@$$hYAHXZ

__vectorcall

ARM64EC 툴체인은 현재 __vectorcall을 지원하지 않습니다. 컴파일러는 ARM64EC에서 __vectorcall 사용을 검색하면 오류를 내보냅니다.

참고 항목

ARM64EC ABI 및 어셈블리 코드 이해
일반적인 Microsoft C++ ARM 마이그레이션 문제
데코레이트된 이름