| <bgcolor=#96834a> 명령어 집합 | |
| CISC | AMD64●x86● · M68K · 68xx · MOS 65xx · Z80 · 8080 · z/Architecture · VAX |
| RISC | Arm (A64 · A32 · T32) · RISC-V● · MIPS● · DEC Alpha● · POWER PowerPC · CELL-BE LoongArch · OpenRISC · PA-RISC · SPARC · Blackfin · SuperH · AVR32 AVR |
| VLIW EPIC | E2K · IA-64 · Crusoe |
| <rowcolor=#fff> x86 · AMD64 확장 명령어 집합 | |
| 인텔 주도 확장 명령어 | |
| 범용 | |
| SIMD | AVX-512: F · CD · DQ · BW · VL · IFMA · VBMI · VBMI2 · VNNI · VAES · GFNI · BITALG AVX[3]: AVX-VNNI · AVX-IFMA |
| 비트 조작 | BMI1 · BMI2 · ADX |
| 보안 및 암호 | AES-NI · CLMUL · RDRAND · RDSEED · SHA · MPX · SGX · TME · MKTME |
| 가상화 및 기타 | VT-x(VMX) · SMX · TSX |
| AMD 주도 확장 명령어 | |
| SIMD 및 비트 연산 | 3DNow! PREFETCHW · F16C · XOP · FMA FMA4 · FMA3 |
| 비트 조작 | ABM |
| 보안 및 암호 | SME |
| 가상화 및 기타 | AMD-V |
| 공동 표준 (x86 Ecosystem Advisory Group) | |
| SIMD | AVX10 · ACE AVX10: AVX10.1 · AVX10.2 |
| 보안 및 암호 | ChkTag |
| 시스템 및 기타 | FRED |
1. 개요
x87은 인텔이 x86 아키텍처용으로 설계한 부동소수점 연산 명령어 집합이자, 이를 처리하던 보조 프로세서(코프로세서) 계열의 명칭이다.1980년 출시된 인텔 8087을 시초로 하며, UC 버클리의 수치해석학자 윌리엄 카한(William Kahan)이 설계 자문으로 참여하여 데이터 타입과 예외 처리 체계를 정의하였다. 이 설계는 IEEE 754 부동소수점 표준의 근간이 되었다. 내부 연산 형식으로 독자(비표준) 형식인 80비트 확장 정밀도(64비트 가수부, 15비트 지수부, 1비트 부호)를 사용하며, 8단 깊이의 레지스터 스택으로 데이터를 관리한다.
80486DX(1989)부터 CPU 다이에 통합되었고, 이후 SSE, AVX가 등장하면서 사실상 대체되었으나 AMD64에서도 하위 호환용으로 유지되고 있다.
2. 역사
2.1. 8087
1980년 인텔이 인텔 8086/8088용 부동소수점 보조 프로세서로 출시한 제품이다. 당시 x86 본체에는 정수 ALU만 탑재되어 있었기 때문에, 부동소수점 연산이 필요한 환경에서는 별도의 칩을 메인보드 소켓에 장착해야 했다.부동소수점 연산은 과학 계산, 공학 시뮬레이션, 3D, 그래픽 등 한정된 분야에서만 사용되었고 칩 단가도 비쌌기 때문에 일반 사용자는 코프로세서 없이 x86 시스템을 운용하는 경우가 대부분이었다. 코프로세서가 없어도 소프트웨어 에뮬레이션을 통해 부동소수점 연산은 가능했으나 속도가 크게 떨어졌다.
2.2. 80287, 80387
이후 출시된 x86 프로세서에 맞추어 인텔 80286용 80287, 인텔 80386용 80387이 차례로 출시되었다. 80386 시스템에 80287을 장착하는 식으로 하위 버전 코프로세서를 혼용하는 구성도 가능했다. 이 시기에는 서드파티 제조사들도 x87 호환 명령어 집합을 구현한 자체 코프로세서를 제작하였다.80387은 x87 계열에서 IEEE 754-1985를 완전히 준수한 최초의 제품이다. FSIN, FCOS, FSINCOS 등 삼각 함수 명령어가 이 세대에서 추가되어, 기존의 FPTAN과 FPATAN만으로 삼각 함수를 합성해야 했던 불편이 해소되었다.
2.3. 80486 통합과 487SX
1989년 출시된 인텔 80486 DX부터 x87 유닛이 CPU 내부에 집적되었다. 회로 소형화에 성공하여 통합이 가능해졌으나 그 부작용으로 가격이 크게 상승하였고, 인텔은 FPU를 비활성화한 보급형 486SX와 짝을 이루는 487SX를 함께 출시하였다.487SX는 형식상 코프로세서였으나 실제로는 FPU가 포함된 486DX의 재포장 버전으로, 장착 시 486SX의 CPU를 정지시키고 모든 명령을 자신이 처리하는 구조였다. 시장에서는 같은 클럭의 486SX와 486DX의 성능 차이가 크지 않았음에도 소비자 다수가 486DX를 선택하였고, 487SX는 큰 호응을 얻지 못하였다.
2.4. SSE로의 대체
1999년 발표된 SSE가 128비트 전용 SIMD 레지스터(XMM)를 도입하면서 부동소수점 연산의 주력 자리를 가져갔다. 2000년의 SSE2는 배정밀도(64비트) 부동소수점까지 SSE 레지스터에서 처리할 수 있도록 확장되어 x87을 사실상 대체하였고, 이후 AVX가 그 뒤를 이었다.AMD64에서는 x87을 하위 호환용으로만 유지하고 추가 확장을 하지 않는 방향으로 정리되었다. 64비트 호출 규약에서도 부동소수점 인자 전달과 반환은 XMM 레지스터를 사용하며, x87 스택은 레거시 코드 외에는 거의 사용되지 않는다.
3. 상세
3.1. 프로그래밍 모델
x87은 8개의 80비트 물리 레지스터(R0~R7)를 스택 구조로 운용한다. 프로그래머는 물리 레지스터 번호가 아닌 스택 최상단(TOS)으로부터의 상대 위치인 ST(0)~ST(7)로 레지스터를 참조한다.스택 모델이 채택된 주된 이유는 명령어 인코딩 공간의 부족이다. 8087은 x86의 ESC 명령어 메커니즘을 통해 동작하는데, 첫 바이트에서 ESC 접두사(11011₂)가 5비트를 차지하여 남은 인코딩 공간이 극히 제한적이었다. ST(0)을 암시적 피연산자로 두는 스택 모델은 피연산자 하나를 3비트로 지정할 수 있어 이 예산에 부합했다. 그러나 이 결정은 이후 컴파일러 구현 난이도를 높이게 되었고, 사후 평가에서 설계상 실수로 여겨지게 되었다.
3.2. 80비트 확장 정밀도
x87의 80비트 확장 정밀도 형식은 1비트 부호, 15비트 지수, 64비트 가수부로 구성된다. 단정밀도(32비트)와 배정밀도(64비트)가 정규화된 수의 가수부 최상위 1을 생략하는 숨김 비트(hidden bit) 방식을 사용하는 것과 달리, 확장 정밀도는 정수 비트(J-bit)를 명시적으로 저장한다. 이 설계 덕분에 숨김 비트를 복원하는 연산이 불필요하고, 언더플로로 인한 정밀도 손실을 후속 연산에서 추적할 수 있다.3.3. 명령어 목록
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[x87/명령어 목록#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[x87/명령어 목록#|]] 부분을 참고하십시오.4. 문제점 및 한계
4.1. 결과의 비결정성
x87의 가장 근본적인 문제는 동일한 소스 코드에서도 조건에 따라 다른 수치 결과가 나올 수 있다는 점이다. x87은 모든 연산을 80비트 확장 정밀도로 수행하지만 메모리에 저장할 때는 32비트 또는 64비트로 반올림한다. 컴파일러의 레지스터 할당에 따라 어떤 변수가 레지스터(80비트)에 머무르고 어떤 변수가 메모리(64비트)에 임시 저장되느냐가 달라지므로, 최적화 수준을 바꾸는 것만으로 연산 결과가 달라질 수 있다. GCC 버그 #323은 이 현상의 대표적 사례로, 같은 변수를 비교하는a == a가 거짓을 반환하는 상황이 보고되었다.운영 체제 간 기본 설정 차이도 재현성을 떨어뜨렸다. Windows의 MSVC는 프로그램 시작 시 정밀도 제어(PC)를 53비트로 설정하는 반면 Linux의 glibc는 64비트를 유지하여, 동일 프로그램이 플랫폼에 따라 다른 결과를 낼 수 있었다. 32비트(x87)와 64비트(SSE2) 빌드 간 수치 불일치도 같은 맥락이다.
이러한 비결정성은 SSE2가 64비트 또는 32비트 레지스터에서 선언된 타입의 정밀도로 직접 연산하게 되면서 근본적으로 해소되었다. AMD64가 SSE2를 필수 사양으로 포함한 이후 64비트 환경의 컴파일러는 x87 대신 SSE2를 기본으로 사용하므로, 이 문제는 원천적으로 발생하지 않는다.
4.2. 표준 준수의 한계
x87은 IEEE 754 표준의 모태가 된 설계이지만, 정작 x87 자체는 이 표준의 배정밀도(binary64) 연산을 엄밀히 재현하기는 어렵다.IEEE 754 배정밀도(binary64)는 가수부 53비트, 지수부 11비트(범위 ±1023)로 구성된다. x87 제어 워드의 정밀도 제어(PC) 필드를 53비트로 설정하면 가수부는 배정밀도와 같은 폭으로 반올림되지만, 지수부는 PC와 무관하게 항상 15비트(범위 ±16383)를 유지한다. 따라서 PC 설정만으로는 binary64 연산을 정확히 재현할 수 없으며, 다음과 같은 불일치가 발생한다.
- 오버플로: binary64에서 약 1.8 × 10308을 넘는 결과는 무한대([math(∞)])로 처리되지만, x87 레지스터에서는 약 1.2 × 104932까지 정상 값으로 유지된다. 64비트 메모리에 저장하는 시점에야 비로소 무한대로 변환된다.
- 언더플로: binary64에서 약 2.2 × 10−308 미만의 값은 비정규수가 되거나 0으로 밀리지만, x87 내부에서는 약 3.4 × 10−4932까지 정규수로 유지된다. 저장 시점에야 비정규수나 0으로 변환된다.
이러한 "지연된 오버플로/언더플로"로 인해 x87 레지스터에 머무르는 중간 결과와 메모리에 저장된 결과가 서로 다른 경로를 밟게 되고, 처음부터 binary64로 계산한 결과와 달라질 수 있다. 한편 SSE2에서는 배정밀도(64비트) 또는 단정밀도(32비트) 단위로 직접 연산하여 지수 범위 불일치가 존재하지 않으므로, 별도의 우회 없이 IEEE 754를 준수할 수 있다.
Java의 HotSpot JVM은 strictfp(엄격한 IEEE 754 표준 준수) 구현을 위해 연산마다 메모리 왕복 또는 스케일링 우회를 삽입해야 했으며, 이 비용이 JDK 1.2에서 엄격한 부동소수점 의미론을 완화하는 직접적 계기가 되었다.
4.3. 초월함수의 정확도 문제
FSIN과 FCOS, FSINCOS의 다항 근사에 사용되는 내부 원주율 상수가 66비트에 불과하여, 원주율 [math(π)]의 배수에 가까운 입력에서 최악 약 1.3 × 1018 ULP의 오차가 발생할 수 있다는 점도 널리 알려진 한계이다. 인텔은 오랫동안 문서에 최대 1 ULP 오차를 명시하고 있었으나 2014년 이를 공식 인정하고 문서를 개정하였다.SSE 이후의 SIMD 확장은 삼각 함수나 로그 같은 초월 함수를 하드웨어 명령어로 제공하지 않으며, 현대 수학 라이브러리(glibc libm, Intel SVML 등)는 x87 초월 함수 명령어를 사용하지 않고 소프트웨어로 CORDIC 알고리즘을 직접 구현한다.
5. 관련 항목
- x86
- MMX
- 스트리밍 SIMD 확장(SSE)
- 고급 벡터 확장(AVX)