RV64I, RV32E, 그리고 RV128
5장의 RV32I는 레지스터가 32비트였다. 서버와 스마트폰은 4 GiB보다 큰 메모리를 다뤄야 하므로 레지스터와 주소를 64비트로 넓힌 RV64I가 필요하다. 반대로 아주 작은 마이크로컨트롤러는 레지스터 32개조차 부담스러워 레지스터를 16개로 줄인 RV32E를 쓴다. 이 장에서는 베이스 ISA가 넓어지고 좁아질 때 무엇이 바뀌고 무엇이 그대로 남는지, 그리고 한때 규격에 있었던 RV128I가 지금 어떤 상태인지를 살펴본다.
- XLEN=64가 되면 레지스터·pc·주소 공간·시프트 양이 어떻게 달라지는지 안다.
- RV64I가 새로 더한 12개 명령(
ld·sd·lwu와 9개의 *W 명령)의 역할을 구별한다. - “32비트 값은 64비트 레지스터에 부호 확장된 꼴로 보관한다”는 규칙과 그 덕분에 비교·분기가 그대로 동작하는 이유를 설명한다.
- RV64에서
lui·auipc의 부호 확장과, 64비트 상수를 만드는li전개(최대 8개 명령)를 이해한다. - RV32E/RV64E가 레지스터를 16개로 줄인 이유와 ILP32E 호출 규약의 차이를 안다.
- RV128I가 v20260120 규격에서 어떤 상태인지, RV64 하트가 RV32 코드를 돌리는 방법(UXL·SXL)을 안다.
XLEN=64: 무엇이 넓어지나
규격은 정수 레지스터의 비트 폭을 XLEN(integer register width)이라 부른다. RV64I는 RV32I 위에 지어진 베이스 ISA로, 규격의 RV64I 장은 “RV32I와 다른 점만” 적는다. 핵심은 한 줄이다. 정수 레지스터 x1–x31과 pc, 그리고 사용자 주소 공간이 64비트로 넓어진다. 명령어 길이는 여전히 32비트이고, 형식(R·I·S·B·U·J)과 opcode 배치도 RV32I 그대로다. 그래서 add는 RV64에서 그냥 64비트 덧셈이 된다.
RV32I와 RV64I의 차이를 표로 모으면 생각보다 짧다.
| 항목 | RV32I | RV64I |
|---|---|---|
| 정수 레지스터·pc 폭 | 32비트 | 64비트 |
| 사용자 주소 공간 | 2³² 바이트 | 2⁶⁴ 바이트 (실제로 쓸 수 있는 범위는 실행 환경이 정한다) |
| 명령어 길이와 형식 | 32비트, R·I·S·B·U·J | 같음 |
| 즉시값 시프트의 shamt | 5비트 (0–31) | 6비트 (0–63) |
sll·srl·sra가 보는 시프트 양 | rs2[4:0] | rs2[5:0] |
lui·auipc 결과 | 32비트 그대로 | 32비트 결과를 64비트로 부호 확장 |
| 적재·저장 | lb lh lw lbu lhu sb sh sw | + ld, lwu, sd (lw는 부호 확장) |
| 32비트 연산 전용 명령 | 필요 없음 (모든 연산이 32비트) | *W 명령 9개 |
상수 하나를 만드는 li | 최대 2개 명령 | 최대 8개 명령 (이 장의 방식 기준) |
규격 서론은 “왜 RV32I를 RV64I의 엄격한 부분집합으로 만들지 않았나”라는 질문에 직접 답한다. 베이스 ISA를 따로 두면 각자 자기 폭에 맞게 최적화할 수 있다는 것이다. 예를 들어 add는 RV32I에서 32비트 덧셈, RV64I에서 64비트 덧셈이다. 같은 기계어가 두 베이스에서 다른 결과를 낼 수 있으므로, RV64 하트가 RV32 프로그램을 돌리려면 “지금은 32비트 모드”라고 알려 주는 장치가 필요하다(마지막 절).
RV64I가 더한 12개 명령
RV64I가 새로 더한 명령은 열두 개뿐이다. 64비트를 메모리와 주고받는 ld·sd, 32비트를 0 확장으로 읽는 lwu, 그리고 32비트 값을 위한 *W 명령 9개다. *W 명령은 두 개의 새 major opcode, OP-IMM-32(0011011)와 OP-32(0111011)를 쓴다. 이름 그대로 OP-IMM과 OP의 “32비트 판”이다.
OP-IMM-32와 OP-32는 RV64I가 *W 명령을 위해 쓰는 opcode다. 파란 칸은 RV32I와 같은 opcode이지만 RV64I가 새 funct3(ld·lwu·sd)나 6비트 shamt를 더한 곳이다. 칸 이름은 v20260120 규격의 표를 따랐다.| 명령 | opcode | funct3 | funct7 | 뜻 |
|---|---|---|---|---|
| ld | LOAD 0000011 | 011 | — | 64비트 적재 |
| lwu | LOAD 0000011 | 110 | — | 32비트 적재, 0 확장 (lw는 부호 확장) |
| sd | STORE 0100011 | 011 | — | 64비트 저장 |
| addiw | OP-IMM-32 0011011 | 000 | — | 32비트 덧셈 후 부호 확장. addiw rd, rs, 0 = sext.w |
| slliw | OP-IMM-32 | 001 | 0000000 | 32비트 왼쪽 시프트 (shamt 5비트) |
| srliw | OP-IMM-32 | 101 | 0000000 | 32비트 논리 오른쪽 시프트 |
| sraiw | OP-IMM-32 | 101 | 0100000 | 32비트 산술 오른쪽 시프트 |
| addw | OP-32 0111011 | 000 | 0000000 | 32비트 덧셈 |
| subw | OP-32 | 000 | 0100000 | 32비트 뺄셈. subw rd, x0, rs = negw |
| sllw | OP-32 | 001 | 0000000 | 32비트 왼쪽 시프트, 양은 rs2[4:0] |
| srlw | OP-32 | 101 | 0000000 | 32비트 논리 오른쪽 시프트 |
| sraw | OP-32 | 101 | 0100000 | 32비트 산술 오른쪽 시프트 |
눈여겨볼 점은 *W가 붙은 명령이 덧셈·뺄셈·시프트뿐이라는 것이다. andw, orw, xorw, sltw 같은 명령은 없다. 비교 명령과 논리 연산은 64비트 판을 그대로 써도 32비트 결과가 맞게 나오기 때문이다. 그 이유가 다음 절의 주제다.
32비트 값은 부호 확장해서 보관한다
C의 int나 unsigned int는 RV64에서도 32비트다. 이런 값을 64비트 레지스터에 어떻게 담을지 정해 두지 않으면, 연산할 때마다 위쪽 32비트를 정리하는 명령이 붙어야 한다. RV64I는 컴파일러와 호출 규약이 지키는 하나의 약속, 즉 부호 확장 불변식(sign-extension invariant)으로 이 문제를 푼다.
“The compiler and calling convention maintain an invariant that all 32-bit values are held in a sign-extended format in 64-bit registers. Even 32-bit unsigned integers extend bit 31 into bits 63 through 32.”
Volume I, RV64I · Integer Computational Instructions — 부호 없는 32비트 값까지 bit 31을 위로 복사한다는 점이 핵심이다.
부호 없는 값까지 부호 확장한다는 말이 처음엔 이상하게 들린다. 0xFFFFFFFF(부호 없는 4294967295)가 레지스터에서는 0xFFFFFFFF_FFFFFFFF로 보관된다는 뜻이다. 그런데 이렇게 하면 놀라운 일이 생긴다. 64비트 sltu·bltu로 비교해도 32비트 부호 없는 비교와 결과가 같다. 아래 그림이 그 이유다.
sltu·bltu·bgeu가 32비트 부호 없는 비교 결과를 그대로 낸다. 부호 있는 비교(slt·blt)는 부호 확장의 정의상 당연히 맞는다.규격은 이 불변식에서 따라 나오는 결과를 이렇게 정리한다.
- 부호 있는 ↔ 부호 없는 32비트 변환은 아무 명령도 필요 없다. 비트 모양이 같기 때문이다.
- 부호 있는 32비트 → 64비트 변환도 공짜다. 이미 부호 확장되어 있다.
- 64비트
sltu와 부호 없는 분기가 32비트 부호 없는 값에도 맞게 동작한다(그림 6-3). - 64비트 논리 연산(
and·or·xor)은 부호 확장된 두 값을 받으면 결과도 부호 확장된 꼴이다. 위쪽 32비트가 모두 bit 31의 복사본이니, 비트마다 같은 연산을 하면 결과의 위쪽도 결과 bit 31의 복사본이 된다. - 그래서 새로 필요한 것은 덧셈·뺄셈·시프트뿐이다. 이 연산들은 32비트 결과의 bit 31을 넘어 자리올림이 생기거나(덧셈), 위쪽 비트가 아래로 들어오기 때문에(오른쪽 시프트) 64비트 판으로는 정규형이 깨진다.
*W 명령의 정의는 단순하다. 입력의 위쪽 32비트는 무시하고, 아래 32비트로 계산한 뒤, 결과 32비트를 64비트로 부호 확장해 쓴다. 그러니 결과는 언제나 정규형이다. 아래 시뮬레이터로 64비트 판과 W 판의 차이를 비트 단위로 비교해 보자.
add vs addw: 부호 확장 탐색기
add
addw
add는 0x00000000_80000000(양수 2³¹)을, addw는 0xFFFFFFFF_80000000(−2³¹)을 낸다. 64비트 결과는 더 이상 “부호 확장된 32비트 값”이 아니다(빨간 테두리). 규격은 RV64I에서 add와 addw의 결과를 비교하면 32비트 부호 있는 덧셈의 오버플로를 검사할 수 있다고 말한다. 또 위쪽 쓰레기 무시를 눌러 W 명령이 입력의 위쪽 32비트를 정말로 보지 않는지 확인하자.addiw rd, rs, 0은 아래 32비트를 부호 확장하는 sext.w 의사 명령이다. 반대로 0 확장(zext.w)은 베이스 ISA만으로는 slli rd, rs, 32 + srli rd, rd, 32 두 명령이 필요하다. Zba 확장의 add.uw가 있으면 한 명령(zext.w)으로 줄어든다(14장). 배열 인덱스로 쓰는 unsigned int를 주소에 더할 때 자주 나온다.
시프트: shamt가 6비트로
64비트 값을 0–63칸 시프트하려면 시프트 양(shamt)이 6비트 필요하다. RV32I의 slli는 I형의 즉시값 12비트 중 아래 5비트를 shamt로, 위 7비트를 funct7처럼 쓴다. RV64I는 여기서 funct7의 맨 아래 비트(inst[25])를 shamt[5]로 빌려 온다. 그래서 RV64의 즉시값 시프트는 6비트 funct6 + 6비트 shamt 꼴이 된다. 산술 시프트를 고르는 bit 30은 그대로다.
slliw에서는 funct7의 일부(0)이지만, RV64I의 slli·srli·srai에서는 shamt의 6번째 비트가 된다. 같은 기계어 0x02051513(bit 25=1)은 RV64I에서 slli a0, a0, 32이지만 RV32I에서는 정의된 명령이 아니다.레지스터로 시프트 양을 주는 sll·srl·sra도 같은 원리다. RV64I에서는 rs2의 아래 6비트를 보고, 32비트 판인 sllw·srlw·sraw는 아래 5비트(rs2[4:0])만 본다.
예전 규격은 slliw·srliw·sraiw에서 imm[5] ≠ 0이면 잘못된 명령 예외를 일으키라고 했다. 지금 규격은 이 인코딩을 reserved로 바꿨고, 이는 하위 호환되는 변경이라고 밝힌다. 예외를 “반드시” 일으킬 필요가 없어진 대신, 나중에 표준 확장이 그 자리를 쓸 수 있게 된 것이다. 같은 이유로 RV32E/RV64E 장도 “x16–x31을 쓰는 인코딩은 reserved”라고 적는다.
lui·auipc와 64비트 상수 만들기
lui와 auipc는 RV64I에서도 같은 opcode를 쓴다. 다른 점은 결과의 폭이다. lui는 20비트 즉시값을 위쪽에 놓고 아래 12비트를 0으로 채운 32비트 결과를 64비트로 부호 확장한다. auipc도 같은 32비트 오프셋을 부호 확장한 뒤 pc에 더한다.
lui. 만들어진 32비트 값의 bit 31이 1이면 위쪽 32비트가 모두 1로 채워진다(금색). 그래서 lui 하나로는 0x00000000_80000000 같은 “bit 31만 켜진 양수”를 만들 수 없다. 아래 상자는 규격이 밝힌, 상위 즉시값과 12비트 오프셋을 짝지어 닿을 수 있는 범위다.li는 최대 몇 개의 명령이 될까
RV32에서 아무 32비트 상수나 lui + addi 두 명령이면 만들 수 있었다(4장). 64비트 상수는 그렇게 되지 않는다. 즉시값은 한 번에 최대 20비트(lui)와 12비트(addi)뿐이고, 레지스터 위쪽에 값을 직접 넣는 명령이 없기 때문이다. 컴파일러와 어셈블러는 다음과 같이 재귀적으로 상수를 만든다(LLVM의 RISCVMatInt를 단순화한 방식).
- 값이 부호 있는 32비트 범위에 들면
lui+addiw(또는 작은 값이면addi하나)로 끝낸다.addiw를 쓰는 까닭은 0x7FFFF800–0x7FFFFFFF처럼lui가 0x80000으로 반올림되어 음수가 된 경우에도 32비트 덧셈이 넘쳐 다시 올바른 양수로 돌아오기 때문이다. - 그렇지 않으면 아래 12비트를 부호 있는 값 lo로 떼어 낸다. 남은 값(value − lo)은 아래 12비트가 0이므로 뒤쪽 0의 개수만큼 오른쪽으로 민다.
- 민 값을 같은 방법으로 만든 뒤(재귀),
slli로 되돌리고addi lo를 더한다.
한 번 재귀할 때마다 많아야 두 명령(slli, addi)이 붙고, 최악의 경우 8개 명령이 된다. 이 책의 구현을 무작위 64비트 값 20만 개로 시험해 보면 모두 8개 이하였다.
64비트 상수 빌더
lui 한 개면 되지만, RV64에서는 lui 0x80000이 음수(0xFFFFFFFF_80000000)가 되므로 addi 1 + slli 31로 만든다. 0x7ffff800에서는 addiw가 32비트에서 넘쳐 양수로 돌아오는 모습을 보자. 금색으로 깜빡이는 칸이 그 명령이 바꾼 비트다.이 장의 방식은 이해하기 쉬운 기본형이다. LLVM과 GCC는 여기에 “1을 넣고 왼쪽으로 민 뒤 srli로 앞쪽 0을 만드는” 요령이나, Zba·Zbb·Zbs 확장 명령을 섞어 더 짧은 열을 찾는다. 예를 들어 0x7FFFFFFF_FFFFFFFF는 addi −1 + srli 1 두 명령이면 된다(이 시뮬레이터는 3개). 또 8개 명령짜리 상수는 아예 메모리 상수 풀에서 ld로 읽어 오는 편이 나을 때도 있다.
RV32E와 RV64E: 레지스터를 16개로
반대 방향의 변형도 있다. RV32E(E = embedded)와 RV64E는 각각 RV32I·RV64I에서 정수 레지스터를 x0–x15의 16개로 줄인 것 말고는 바뀐 것이 없다. 명령어 인코딩도 같고, rd·rs1·rs2 필드도 여전히 5비트다. 다만 x16–x31을 가리키는 인코딩은 모두 reserved가 된다. v20260120 규격에서 RV32E와 RV64E는 버전 2.0으로 비준된 베이스 ISA다.
“We have found that in the small RV32I core implementations, the upper 16 registers consume around one quarter of the total area of the core excluding memories, thus their removal saves around 25% core area with a corresponding core power reduction.”
Volume I, RV32E and RV64E · Programmers’ Model — RV64E는 큰 SoC 안의 마이크로컨트롤러나, 문맥 상태를 줄이고 싶은 다중 스레드 64비트 프로세서를 위해서도 관심을 받는다고 덧붙인다.
ILP32E 호출 규약
레지스터가 절반이니 함수 호출 약속도 달라진다. psABI의 ILP32E 규약은 일반 정수 호출 규약과 같되 다음이 다르다(7장).
| 항목 | ILP32 (RV32I) | ILP32E (RV32E) |
|---|---|---|
| 인자 레지스터 | a0–a7 (8개) | a0–a5 (6개) |
| 피호출자 저장 레지스터 | s0–s11 (12개) | s0–s1 (2개) |
| 임시 레지스터 | t0–t6 (7개) | t0–t2 (3개) |
| 스택 포인터 정렬 | 16바이트 | 4바이트(32비트 경계)면 충분 |
| D 확장과 함께 | 가능 (ILP32D) | 쓸 수 없다 |
리눅스나 이 책의 에뮬레이터는 a7(x17)에 시스템 호출 번호를 넣는다. RV32E에는 x17이 없으므로 그런 약속은 그대로 쓸 수 없고, RV32E용 실행 환경은 x0–x15 안의 다른 레지스터를 쓰는 별도 약속을 정해야 한다. 또 psABI v1.0 문서는 ILP32E가 GCC의 현재 구현을 기록한 것이며 바뀔 수 있다고 적어 두었다.
RV32E 호환성 검사기
어셈블리 (riscv.js로 어셈블한 뒤 레지스터 필드를 검사한다)
정수 레지스터 파일
코어 면적 (메모리 제외, 규격 비고의 어림값)
호출 규약
a6은 x16, s2는 x18, t3은 x28이다. 기계어의 rd·rs1·rs2 필드가 16 이상이면 그 인코딩은 RV32E에서 reserved다. 위쪽 단추로 RV32I로 바꾸면 같은 코드가 모두 통과한다.RV128I는 어디로 갔나
RISC-V는 처음부터 주소 공간이 64비트로도 모자랄 날을 대비했다. 2014년의 사용자 ISA 2.0 문서에 128비트 주소 공간 변형 RV128이 처음 서술되었고, 2017년 2.2판과 2019년 비준판(20191213)에도 RV128I 1.7 (Draft) 장이 있었다. 레지스터를 128비트로 넓히고, *W처럼 64비트 값을 다루는 *D 명령을 두는 식의 스케치였다. 하지만 이 장은 끝내 동결·비준되지 않았다.
그렇다고 128비트의 문이 닫힌 것은 아니다. v20260120의 서론은 지금도 “RISC-V에는 현재 네 개의 베이스 ISA가 있다”고 말하면서, 다음과 같이 여지를 남긴다.
“A larger flat 128-bit address space might eventually be required and could be accommodated with a new RV128I base ISA within the existing RISC-V ISA framework.”
흔적은 다른 곳에도 있다. 특권 규격의 misa.MXL 필드는 1이면 XLEN=32, 2이면 64를 뜻하고, 예전에 128을 뜻하던 3은 지금 Reserved로 비어 있다. 또 예전 판 opcode 지도에서 custom-2·custom-3 칸에는 “rv128”이라는 꼬리표가 붙어 있었지만(RV128이 쓸 수도 있다는 뜻), 지금 지도(그림 6-2)에는 없다.
RV64 하트에서 RV32 코드 돌리기
앞에서 본 대로 RV32I는 RV64I의 부분집합이 아니다. add 하나도 두 베이스에서 결과의 폭이 다르다. 그래서 RV64 하트가 RV32 프로그램을 돌리려면 하드웨어가 “이 모드에서는 XLEN이 32다”라고 알아야 한다. 특권 규격은 이것을 mstatus CSR의 두 필드로 제공한다(17장).
mstatus의 SXL(bit 35:34)과 UXL(bit 33:32)이 S 모드와 U 모드의 XLEN을 정한다. 예를 들어 64비트 커널(SXL=2) 위에서 옛 32비트 앱을 돌리려면 UXL=1로 둔다. 다만 규격은 UXLEN이 SXLEN보다 커지는 값은 허용하지 않는다.| 상황 | 규격의 규칙 (특권 규격 · Base ISA Control in mstatus) |
|---|---|
| MXLEN = 32 | SXL·UXL 필드가 없다. SXLEN = UXLEN = 32. |
| MXLEN = 64, S 모드 없음 | SXL은 읽기 전용 0. |
| MXLEN = 64, S 모드 있음 | SXL은 SXLEN을 담는 WARL 필드. SXLEN = MXLEN으로 고정해도 된다. |
| MXLEN = 64, U 모드 있음 | UXL은 UXLEN을 담는 WARL 필드. UXLEN = MXLEN 또는 SXLEN으로 고정해도 되고, UXLEN > SXLEN 이 되는 값은 쓸 수 없다. |
규격 서론에는 “RV32I의 ADD와 RV64I의 ADDW를 같은 인코딩으로 했으면 어땠을까”라는 질문도 나온다. 그렇게 하면 32비트 덧셈이 두 베이스에서 같은 기계어가 되어 더 일관되었을 것이다. 초기 RISC-V에는 실제로 그런 설계가 있었지만 2011년 1월에 지금 방식으로 바뀌었다. 규격은 돌이켜 보면 근거가 충분하지 않았을 수 있다고 인정하면서도, 위에서 본 이유(어차피 모드 전환이 필요하고, 호출 규약과 시스템 호출이 달라 32비트 라이브러리를 64비트 코드에 섞어 쓰기는 비현실적)로 실질적인 손해는 적다고 정리한다.
핵심 정리
- RV64I는 x1–x31, pc, 사용자 주소 공간을 64비트로 넓힌다. 명령어 길이와 형식은 그대로이며,
add같은 기존 명령은 64비트 연산이 된다. - 새 명령은 12개다:
ld·sd·lwu와 *W 명령 9개(addiw slliw srliw sraiw addw subw sllw srlw sraw). *W는 입력의 위쪽 32비트를 무시하고 32비트 결과를 부호 확장한다. - 32비트 값은 부호 없는 값까지 부호 확장된 꼴로 보관한다. 그래서 부호 변환이 공짜이고, 64비트 비교·분기·논리 연산이 32비트 값에도 그대로 맞는다.
- 즉시값 시프트의 shamt는 6비트가 되어 inst[25]가 shamt[5]로 쓰인다.
slliw·srliw·sraiw에서 imm[5] ≠ 0은 reserved다. lui·auipc는 32비트 결과를 부호 확장한다. 64비트 상수li는 재귀적 전개로 최대 8개 명령이 된다.- RV32E/RV64E는 레지스터를 16개로 줄인 비준 베이스 ISA(2.0)다. 작은 코어에서 면적을 약 25% 줄이며, x16–x31 인코딩은 reserved다.
- RV128I 스케치는 20240411판에서 삭제되었고, v20260120에는 서론의 한 문장과
misa.MXL의 예약값 3만 남아 있다. RV64 하트는mstatus.UXL/SXL로 낮은 모드의 XLEN을 32로 바꿀 수 있다(선택 사항).
확인 퀴즈
RV64I에서 a1 = 0x00000000_7FFFFFFF, a2 = 1일 때 addw a0, a1, a2의 결과는?
add는 0x00000000_80000000이므로 두 결과를 비교하면 32비트 오버플로를 알 수 있다.RV64에서 32비트 부호 없는 값도 부호 확장해 보관하는 가장 큰 이점은?
RV64I의 slli에서 inst[25]의 역할은?
RV64I에서 lui a0, 0x80000을 실행한 뒤 a0의 값은?
lui는 32비트 결과 0x80000000을 64비트로 부호 확장한다. 그래서 양수 0x80000000을 만들려면 다른 방법(예: addi 1 + slli 31)이 필요하다.RV32E 하트에서 add x16, x1, x2의 인코딩은 규격상 어떤 상태인가?
v20260120 규격에서 RV128I의 상태로 옳은 것은?
misa.MXL의 값 3도 Reserved다.64비트 리눅스 커널(S 모드) 위에서 32비트 RISC-V 사용자 프로그램을 돌리려면 무엇을 바꿔야 하나?
misa.MXL은 읽기 전용이다.이 장의 재귀적 li 전개에서 임의의 64비트 상수에 필요한 명령 수의 최댓값은?
lui+addiw 두 개에서 시작해 재귀할 때마다 slli+addi가 붙는다. 0x123456789abcdef0 같은 값은 8개 명령이 된다.더 읽을거리
- The RISC-V Instruction Set Manual, Volume I — RV64I Base Integer Instruction Set, Version 2.1 (v20260120)
- Volume I — RV32E and RV64E Base Integer Instruction Sets, Version 2.0
- Volume I — Introduction (베이스 ISA 네 개, RV128I 언급, ADD/ADDW 설계 이유)
- Volume I — Preface (RV128I 스케치 삭제 기록)
- Volume I — RV32/64G Instruction Set Listings (opcode 지도)
- Volume II — Machine-Level ISA: misa.MXL, mstatus SXL/UXL
- RISC-V psABI v1.0 — Procedure Calling Convention (ILP32E)
- LLVM 프로젝트
RISCVMatInt.cpp— 이 장의 64비트 상수 전개가 본뜬 상수 생성 알고리즘