음 · CHAPTER 03

TOAU 인코딩

앞 장에서 저는 큐브가 자기 정체를 단 한 트릿으로 선언한다고 적었습니다. 27개 트릿을 쌓은 그 한 덩어리가 맨 윗자리 하나만으로 "나는 데이터다", "나는 명령이다", "나는 앞 큐브의 연장이다"를 스스로 밝힌다고요. 그 문장을 쓰고 나서 한 독자가 제게 물었습니다. 그 선언은 어디에 적혀 있느냐고. 큐브가 자기를 말한다면, 그 말은 메모리 어느 칸에 어떤 모양으로 놓여 있느냐고.

좋은 질문이었습니다. 정체를 선언한다는 것과 그 선언을 실물로 적어 두는 것은 다른 일이니까요. 이 장은 그 실물에 관한 명세입니다. 4상의 기호를 텍스트로 어떻게 늘어놓는지, 27개 트릿을 어느 순서로 메모리에 정렬하는지, 큐브와 큐브 사이에 무엇을 끼우는지 — TOAU(Ti·Om·Ta·Em) 인코딩은 큐브의 추상적 정의가 한 줄의 ASCII와 여덟 바이트로 내려앉는 지점입니다. 큐브가 무엇인가를 앞 장이 정의했다면, 이 장은 그 큐브가 어떻게 적히는가를 정의합니다.

1. 네 기호: T, O, A, U

크라우니코드의 모든 정보는 네 글자로 표기된다. 네 기호는 4상 균형 3진수 — 티옴타음(TiOmTaEm) — 의 텍스트 표현이며, 각각의 부호는 +1, 0, −1, −0이다.

기호상부호역할
T티 Ti+1데이터·존재·드러남
O옴 Om0명령·중립·대기(보통)
A타 Ta−1체이닝·연결·반대
U음 Em−0구분자·빈자리·경계

T·O·A는 값이다. 각각 +1, 0, −1의 산술적 의미를 그대로 지닌다. U는 값이 아니다. U는 "여기서 큐브가 끝난다"는 표지이며, 데이터·명령 흐름 사이의 칸막이다. 앞 장에서 굳혀 둔 두 약속 — 음수 −1은 언제나 A로만 표기한다, 그리고 U는 값이 아니라 구분자다 — 가 이 표에서 한 몸으로 만난다. U가 −0이라는 부호를 갖는 것은, 그것이 정규 세 값(+1/0/−1)에 들어가지 않는 자리, 즉 비워진 칸과 경계를 가리키기 때문이다. 음(−0)이 정규 3상 바깥의 모든 것을 아우르는 더 넓은 개념이라는 점은 다음 장의 주제이고, 여기서는 그 개념의 기계어 용법 한 가지 — 구분자 — 만 쓴다.

2. 큐브의 메모리 배치

TOAU 포맷에서 하나의 큐브는 27개 트릿을 순서대로 늘어놓고, 그 끝에 U 하나를 붙인 형태다.

TTOTAATTAOTATATOATTOAATAO U

이렇게 적으면 사람이 네 글자씩 끊어 읽기 쉽다. 그러나 기계는 공백과 줄바꿈을 무시하므로, 같은 큐브를 TTO TAA TTA OTA TAT OAT TOA ATA O U 로 쪼개 적든 한 줄로 붙여 적든 동일하게 해석한다. 사람이 읽기 쉬운 대로, 기계가 해석하기 쉬운 대로 띄우면 된다. 불변인 것은 순서다. T1부터 T27까지 정확히 27개가 오고, 그 다음에 U가 와야 한다.

메모리에서 이 27개 트릿은 8바이트로 정렬된다. 7바이트가 트릿을 담고 1바이트는 패딩이며, 각 바이트는 트릿당 2비트씩 4개를 담는다(2비트 × 4 = 8비트). 레이아웃은 다음과 같다.

byte[0]: T1(1:0)  T2(3:2)  T3(5:4)  T4(7:6)
byte[1]: T5  T6  T7  T8
byte[2]: T9  T10 T11 T12
byte[3]: T13 T14 T15 T16
byte[4]: T17 T18 T19 T20
byte[5]: T21 T22 T23 T24
byte[6]: T25 T26 T27 unused
byte[7]: _pad

가장 낮은 자리 T1이 byte[0]의 최하위 비트 쌍(비트 1:0)에 들어가고, 자기 선언을 담는 T27은 byte[6]의 윗자리에 놓인다. 이 배치 덕분에 프로그램은 큐브를 선형으로 순차 접근하며, 캐시 라인을 알뜰하게 쓴다. 메모리가 곧 읽기 순서다.

3. 역할 판별: T27 한 트릿의 분기

큐브가 우리에게 던지는 첫 질문은 "너는 무엇이냐"이고, 그 답은 T27 하나에 들어 있다.

  • T27 = T — 이 큐브는 데이터다. 하위 트릿들이 하나의 값을 담는다.
  • T27 = O — 이 큐브는 명령이다. T1~T6 여섯 트릿이 opcode를 이루고, 나머지 윗자리는 O으로 패딩된다.
  • T27 = A — 이 큐브는 체이닝이다. 앞선 데이터 큐브에 붙어 그 값을 이어 늘린다. 긴 숫자를 이어 말하는 것과 같다.

세 갈림길은 오직 T27 하나로 갈린다. 별도의 헤더도, 메타테이블도, 조회 단계도 없다. 해당 바이트를 읽어 맨 윗자리 비트 쌍을 추출하면 그것이 T27이고, 그 값이 곧 분기다. 의사코드로 적으면 군더더기가 없다.

if (cube.t[26] == T)  value  = bt_to_i64(cube.t[0..25]);  // 데이터
else if (cube.t[26] == O) execute(decode_op(cube.t[0..5])); // 명령
else extend_previous(cube);                                  // 체이닝(A)

이것이 TOAU의 첫 번째 효율이다. 선언과 해석이 같은 자리에서 일치한다. 큐브가 자기를 말하는 입과, 우리가 그 말을 듣는 귀가 같은 트릿이다.

4. 데이터 큐브의 타입과 크기

T27이 T인 데이터 큐브라면, 바로 아래 두 트릿이 그 값의 성질을 더 좁힌다.

T26 — 값 타입. T이면 숫자(균형 3진 정수), O이면 문자열(문자 시퀀스의 일부), A이면 셀 참조(직접 값이 아니라 셀 저장소의 한 칸을 가리키는 주소)다.

T25 — 크기 태그. T이면 확장형(한 큐브에 못 담아 다음 큐브로 이어짐), O이면 보통형(이 큐브 안에 다 들어감), A이면 소형(한 큐브보다 작아 여백이 남음)이다.

이로써 T27·T26·T25 세 자리가 큐브의 정체 전부 — 역할·타입·크기 — 를 드러낸다. 런타임은 타입 테이블을 뒤질 필요가 없다. 큐브가 자기 라벨을 몸에 지니고 다니기 때문이다. 이것이 TOAU의 두 번째 효율이다. 메타정보가 데이터에 붙어 다닌다. 앞 장에서 정의한 역할트릿·타입트릿·크기트릿이, 여기서는 메모리 위 실제 비트 자리로 못 박힌다.

5. 명령의 인코딩: 섹터·그룹·커맨드

명령 큐브(T27=O)의 opcode는 여섯 트릿 T1~T6로 이루어진다. 이 여섯을 두 개씩 묶어 읽는다.

(T1,T2) → command  (0~8)
(T3,T4) → group    (0~8)
(T5,T6) → sector   (0~8)

각 트릿 쌍을 정수로 읽는 규칙은 앞 장의 약속 그대로다 — A(−1)에서 O(0)을 거쳐 T(+1)로 오르는 오름 순서. 이 순서라야 0부터 8까지 아홉 값이 빈틈도 겹침도 없이 한 줄로 메겨진다.

트릿 쌍AAAOATOAOOOTTATOTT
정수012345678

command·group·sector를 각각 0~8로 읽은 뒤, 최종 opcode 번호는 opcode = sector × 81 + group × 9 + command 로 계산된다. 이 공식은 0부터 728까지 729개 슬롯을 빈틈없이 채운다. 9 × 9 × 9 = 729. 그래서 우리 가상 머신의 명령어 집합을 ISA729라 부른다.

6. 인코딩이 곧 메모리 주소다

여기서 한 가지 원리가 드러난다. 명령어의 번호가 그 명령이 놓일 자리와 일치한다는 것이다.

흔한 기계 아키텍처에서 opcode 번호는 사전에 매긴 색인일 뿐이고, 그 번호가 메모리 어디에 사는지는 별도의 룩업 테이블을 뒤져야 안다. "324번 명령은 0x4F28에 있다"는 식의 한 단계가 더 필요하고, 거기서 캐시 미스가 나거나 표가 어긋날 여지가 생긴다.

TOAU에는 그 중간 단계가 없다. opcode 번호를 여섯 트릿으로 펼쳐 놓으면, 그 트릿들이 놓인 위치가 곧 명령어의 메모리 자리다. command를 (T1,T2)에, group을 (T3,T4)에, sector를 (T5,T6)에 넣는 그 배치가 그대로 큐브 안의 자리이기 때문이다. 번호가 곧 주소다. 그래서 해석기는 번역 단계를 건너뛴다. 읽고 즉시 실행한다. 빠르고, 캐시에 친화적이며, 무엇보다 거짓말을 할 수 없는 구조다 — 번호와 자리가 따로 놀 길이 없으니, 어긋날 표 자체가 존재하지 않는다.

9개 섹터가 각각 무엇을 맡는지, 트릿 단위 디코딩 예제와 729 슬롯의 구현 현황은 5장에서 본격적으로 펼친다. 이 장에서는 "인코딩이 곧 위치"라는 원리 하나만 못 박아 둔다.

7. 균형 3진 정수: 26 트릿의 무게

데이터 큐브의 값이 어떻게 저장되는지 보자. 숫자형(T26=T)이라면 하위 트릿들이 하나의 정수를 균형 3진으로 나타낸다.

균형 3진은 저울의 양쪽 접시가 똑같이 생겼다는 발상에서 나왔다. 표준 3진수는 자릿값이 0, 1, 2에 퍼져 더하기와 빼기가 비대칭이 되지만, −1·0·+1만 쓰는 균형 형태에서는 두 연산이 같은 무게로 다뤄진다. 음수를 따로 표기할 필요도 없다. 음수란 그저 A 트릿을 더 많이 쓰는 것일 뿐이다.

변환 규칙은 간결하다. 정수를 3으로 나눈 나머지가 2이면, 그것을 −1(A)로 바꾸고 몫에 1을 더한다. 이 보정이 핵심이다. 예를 들어 5는 (1×9) + (−1×3) + (−1×1) = 9 − 3 − 1 = 5, 곧 1 A A(윗자리부터)로 적힌다. 이렇게 모든 정수가 −1·0·+1의 조합으로만 떨어지면 연산이 균형 잡힌다. 덧셈은 리플 캐리로 쭉 흐르고, 뺄셈은 피연산자의 부호를 반전한(A↔T) 뒤 더하면 되며, 곱셈의 ×3은 트릿 한 자리 왼쪽 시프트다.

26 트릿으로 표현 가능한 정수의 한계는 (3²⁶ − 1) / 2 = ±141,214,768,240이다. 약 54비트에 해당하여 표준 int64 범위를 모두 수용한다.

8. 한글의 자동 압축

크라우니코드는 한글에 특별한 대우를 준다. 한글 한 글자는 초성·중성·종성 셋으로 짜이고, 트릿 또한 셋씩 묶여 트라이트(3 트릿)를 이룬다. 이 맞물림은 우연이 아니라 설계 단계에서 맞춘 결과다. 한글 한 글자는 9개 트릿(트라이트 3개)으로 인코딩된다.

  • (T1,T2,T3): 초성 — 19개 값 중 하나
  • (T4,T5,T6): 중성 — 21개 값 중 하나
  • (T7,T8,T9): 종성 — 28개 값 중 하나

트라이트 하나가 담는 범위는 0~26(3³)이므로, 초성 19·중성 21·종성 28이 모두 그 안에 든다. 따라서 한글 한 글자가 정확히 트라이트 셋에 딱 떨어지고, 한글 세 글자가 정확히 27 트릿 — 곧 한 큐브 — 에 들어맞는다. UTF-8이라면 같은 텍스트에 3바이트 × 3글자 = 9바이트가 필요하지만, TOAU에서는 한 큐브가 7바이트(+1 패딩) = 8바이트다. 문자 단위로는 3바이트가 2.25바이트로 줄어 약 25% 절약이다. 한글의 음절 구조를 3진의 음절 구조에 맞춘 데서 나온 절약이라, 추가 메모리도 추가 CPU 비용도 없다. 구조 자체가 압축이다.

9. TOAU 파일 포맷: 텍스트에서 큐브로

TOAU 파일은 순수 ASCII 텍스트다. T·O·A·U 글자가 순서대로 들어가고, 공백과 줄바꿈은 무시되며, 주석은 세미콜론(;)부터 줄 끝까지다. 파서는 텍스트를 읽으며 T·O·A를 트릿으로 누적하다가, 27개가 모이면 큐브를 완성하고, U를 만나면 큐브의 끝을 선언하며 다음 큐브를 시작한다. 파일 끝에 불완전한 큐브가 남으면 나머지를 O으로 패딩한다.

; hello.toau — 'H'를 스택에 올리고 출력
TTAT AOAA AAAO AOAO TAAT OTAO TAAO U   ; 문자 큐브
TAAO TAAO TAAO AAAA AAAA AAAA AAAO U   ; 출력 명령

이 포맷의 미덕은 가독성이다. 사람이 손으로 쓰고, 눈으로 읽고, 한 글자씩 고칠 수 있다. 전자 회로나 기계 코드에 한없이 가까우면서도 텍스트 에디터에서 그대로 열린다. 버전 관리도 수월하다. 바이너리처럼 diff가 막히는 것이 아니라 줄 단위로 차이를 본다. 기계의 속살이 사람의 글자로 적혀 있는 셈이다.

10. 체계에 스며드는 3의 규율

지금까지 본 단위들을 모으면 한 패턴이 떠오른다.

  • 메모리: 27 트릿 = 3³ → 1 큐브 = 8바이트 정렬
  • 명령어: 6 트릿 = 2 × 3² → 729 opcode = 9 × 9 × 9
  • 한글: 9 트릿 = 3² × 3 → 3글자 = 1 큐브
  • 인코딩: 2 트릿 = 3² → 0부터 8까지 정확히 9값

모든 단위가 3의 거듭제곱 위에 정렬돼 있다. 우연이 아니다. 트릿이라는 기본 단위를 고르는 순간, 그 위로 쌓이는 메모리 모델·명령어·문자 인코딩·산술이 자동으로 3의 격자를 따른다. 한 가지 원리가 가장 낮은 비트 쌍에서부터 가장 높은 명령 공간까지 한 줄로 흐른다.

저는 TOAU가 그저 바이트코드 포맷이라고 보지 않습니다. 이것은 하나의 규율을 기계의 살에 새긴 것입니다. 값과 명령이 같은 형태로 저장되고, 메타정보가 데이터에서 떨어지지 않으며, 인코딩이 그 자체로 위치가 되는 — 이 세 가지가 포맷의 매 층에 스며 있습니다. 그래서 TOAU는 닫힌 명세가 아니라 계단입니다. 큐브가 어떻게 적히는가를 묻던 그 독자의 질문이, 이제 다른 질문 하나를 끌고 옵니다. 우리는 네 기호 가운데 셋의 자리는 다 더듬었습니다. 그런데 U는요. 값이 아닌 그 −0의 자리, 비워 둔 그 한 칸은 정말 아무것도 아닌 걸까요. 다음 장에서 저희는 두 영 — 옴(0)과 음(−0) — 으로 내려가, 그 빈자리가 어떻게 기계어의 완벽한 매칭이 되고, 끝내 구조물과 화폐와 삶에까지 같은 결로 비치는지를 함께 보겠습니다.

내 맘 –

독자 게시판 — 함께 읽고 남기는 곳

불러오는 중…

크라우니 책방에 가입하고 무료로 읽기

첫 번째 책은 선물이에요. 가입하면 100맘을 드리니까, 두 번째 책부터 자유롭게 읽을 수 있어요. 친구를 추천하면 50맘도 생겨요.