aboutsummaryrefslogtreecommitdiffstats
path: root/docs/research/140-spec-suite.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/research/140-spec-suite.md')
-rw-r--r--docs/research/140-spec-suite.md79
1 files changed, 78 insertions, 1 deletions
diff --git a/docs/research/140-spec-suite.md b/docs/research/140-spec-suite.md
index 1da8f4b..89d92e8 100644
--- a/docs/research/140-spec-suite.md
+++ b/docs/research/140-spec-suite.md
@@ -54,7 +54,7 @@
| 5 | UTF-8과 줄 나누기(UAX #14) | 텍스트 | 바이트, 코드 포인트, 속성 표 | Unicode의 `LineBreakTest.txt` | 못 쓴다(글자) | 썼다: LineBreakTest.txt 16,672줄 모두와 무작위 2,000줄이 맞다. 줄 1.8배, C의 199배(아래) |
| 6 | 관계 질의, 그래프 탐색 | 데이터 모델 | 리스트, 키, 접기, 이벤트의 리스트 | TPC-C, 은행 질의, 파이썬 모델 | 된다(기준선: [092](092-tpcc.md), 040의 은행 질의, aup `routes.aup`) | 된다. TPC-C가 SQLite와 오차 없이 같다(5단계) |
| 7 | 텍스트 편집 | UI 개념 | text, 커서 | 편집기의 참조 모델 대조(aup `make check`) | 된다 | 된다 |
-| 8 | 상자 배치(flex 같은) | UI 개념 | 중첩, 재귀 | 기존 배치 구현(Yoga, Taffy)의 결과와 대조 | 못 쓴다(중첩, 재귀) | 중첩과 재귀는 핸들러에서 된다. 보기에서 재귀 fn을 부르지 못한다(탐침). 쓰지 않음 |
+| 8 | 상자 배치(flex 같은) | UI 개념 | 중첩, 재귀 | 기존 배치 구현(Yoga, Taffy)의 결과와 대조 | 못 쓴다(중첩, 재귀) | 썼다(Flexbox의 부분집합): 무작위 트리 2,000개가 Taffy와 0.01 안, 파이썬 참조와 비트까지 같다. 줄 2.1배, Taffy의 7.4배(아래) |
| 9 | HTTP/1.1 요청을 패킷마다(RFC 9112) | 스트림의 상태 기계 | 바이트, 이벤트를 넘는 상태 | 경계를 아무 데서나 자른 요청 모음 | 못 쓴다(바이트). 지금은 `serve/`의 C다 | 썼다: 스트림 200개를 어디서 잘라도 h11과 같다. 우회 하나(리스트 값의 대입), 줄 1.9배, C의 83배(아래) |
| 10 | 재시도, 물러서기, 시간 제한 | 시간 | 시각, 한 번의 타이머 | 시각을 준 시뮬레이션 | 우회(주기 틱 `every`와 `now`) | 썼다(RFC 6298): 사건 241,612개가 같은 규칙의 모델과 같다. 우회 하나(주기 틱: 10 ms면 타임아웃 하나에 틱 2,796개), 줄 1.9배(아래) |
| 11 | X25519, ChaCha20-Poly1305 | 상수 시간 암호 | 데이터에 따라 달라지지 않는 시간 | RFC 7748, RFC 8439의 벡터 | aup 밖(설계상, 아래) | 그대로(설계상 aup 밖) |
@@ -842,6 +842,83 @@
번째 후보: HTTP의 유휴 연결 시간 제한, DNS 질의의 재시도. 둘 다 아직 묶음의 명세가 다루지 않는다.
- 원본: `rto.py`(모델), `rto_check.py`(맞음, 틱의 값, 줄 수), `2026-10-19-rto.jsonl`.
+## 묶음의 8: 상자 배치 (CSS Flexbox 1의 부분집합, 2026-10-19)
+
+**부분집합(쓰기 전에 정했다).** 막대를 셈하려면 알고리즘의 어디까지를 쓰는지 먼저 정해야 한다.
+- 컨테이너: 한 줄(wrap 없음), 방향 row·column(역방향 없음), padding 네 변, 항목 사이의 gap, justify-content 여섯(start, end,
+ center, space-between, space-around, space-evenly), align-items 넷(start, end, center, stretch).
+- 항목: flex-grow와 flex-shrink는 0–3의 정수, flex-basis는 정한 길이, 주축 크기는 auto, 교차 크기는 정한 길이이거나 auto(auto는
+ 늘이는 줄에서만).
+- 최소 크기는 0이다(그래서 바닥은 항목의 padding이다). 최대 크기, margin, border, 절대 위치, 종횡비, align-self, order는 없다.
+ box-sizing은 border-box(Taffy의 기본)다.
+- 내용으로 정하는 크기(intrinsic sizing)는 없다. 모든 크기가 위에서 아래로 정해진다. 뿌리는 정한 너비와 높이를 가진다.
+- 9.7(유연한 길이 결정)은 고정 고리까지 쓴다. 인자가 정수라 "풀린 항목의 인자 합이 1 미만"(4b)은 일어나지 않는다. 그 가지는 이
+ 부분집합 밖이다.
+- 알려진 구현은 Taffy 0.15(cargo로 받은 Rust, `layout_taffy`)다. 반올림을 끄고, 이진32인 Taffy와 이진64인 파이썬 참조의 차는
+ 0.01 안이어야 한다. aup 앱은 파이썬 참조와 비트까지 같아야 한다.
+- **고정 고리가 들어온 까닭.** 처음에는 9.7을 한 번에 끝나는 경우만 쓰고, 고리가 필요한 트리(줄이다 padding 밑으로 내려가는
+ 항목)를 버렸다. 무작위 트리 6,538개를 뽑아 2,000개를 얻는 동안 4,346개(66%)가 그래서 버려졌다. 그 부분집합은 "늘이기만"을 재는
+ 셈이다. 그래서 고리를 썼다. 고리는 돌 때마다 항목을 하나 이상 고정하므로 항목 수만큼 돌면 끝난다.
+
+**쓴 것.** aup의 `examples/layout.aup`이다. 새 명령도 코어의 C도 없다.
+- 트리는 메시지 하나로 온다. 노드의 행(부모의 위치, 방향, 크기, 인자, basis, padding 넷, gap, justify, align)이 앞차례(preorder)로
+ 든다. 부모가 아이보다 먼저 오므로, 앱은 행을 차례로 돌며 노드마다 아이들의 상자를 정한다. 크기가 모두 위에서 오므로 재귀가 필요
+ 없다. 파이썬 참조(`layout.py`)도 같은 행을 같은 차례로 돈다.
+- 상자(x, y, w, h, 부모의 상자에 대해)는 같은 차례의 저장 리스트다.
+
+**맞음.**
+- `layout_check.py`: 무작위 트리 2,000개(노드 29,388개). Taffy의 상자가 모두 파이썬 참조의 0.01 안이다(가장 큰 차 8.3e-5). aup
+ 앱의 상자는 세 실행 길에서 모두 파이썬 참조와 비트까지 같고, 세 길이 같다. 버린 트리는 없다.
+- 9.7의 고리가 padding에서 고정한 항목이 7,659개다(트리 1,399개, 70%). 고리는 시험에서 늘 돈다.
+- Taffy와 맞추며 찾은 것 둘.
+ 1. min-size를 0으로 주면 Taffy는 9.7의 d단계에서 항목을 0에서만 자른다. padding은 항목이 끝에 갖는 크기의 바닥일 뿐이다. 표준은
+ 내용 상자를 0에서 자르라고 한다(border-box에서는 padding이 바닥이다). 그래서 Taffy에 min-size로 padding의 합을 준다. 그 값이
+ 표준의 규칙이 내는 값이다. Taffy의 버릇을 앱에 베끼지 않았다.
+ 2. 파이썬은 3.12부터 `sum()`으로 부동소수를 보정해 더한다(Neumaier). 앱은 왼쪽부터 더하므로 참조도 왼쪽부터 더한다(`reduce`).
+ 처음에는 트리 200개 가운데 15개가 1e-14쯤 어긋났다.
+- Pure의 `make check`(`module_specs`)에 들어갔다. 트리 100개(노드 1,518개)를 상자와 함께 `tests/specs/layout`에 두었다.
+ space-around의 첫 간격을 3으로 나누는 앱은 그 검사에서 떨어진다.
+
+**막대.**
+
+| 막대 | 잰 것 | |
+|---|---|---|
+| 쓸 수 있다 | aup와 Pure만. 새 명령 없음 | 지남 |
+| 우회가 없다 | 마찰 넷(아래). 우회라 할 꼴은 없다 | 지남(마찰은 적는다) |
+| 줄 수 ≤ 파이썬 참조 | aup 116줄(닫는 괄호만인 줄 17) 대 파이썬 54줄 | **못 미침**(2.1배) |
+| C 대비 시간 | 노드당 4,754 ns(묶음), 4,917(라운드), 4,751(모두 컴파일). Taffy(Rust, 트리를 짓고 배치)는 644 ns로, Pure가 **7.4배**다. 순수 파이썬 5,396 ns(Pure가 **1.14배 빠름**) | 기록(막대 없음) |
+| 견고하다 | 위의 맞음, 세 길이 같음. 세 기계는 아직 | 일부 |
+
+**aup에서 고친 것: 메시지의 행을 위치로 읽기.** `tree.nodes[k]`가 행 전체를 리스트 값으로 복사하고(`LIST_VALUE`) 그 복사본에서
+읽었다. 그래서 읽을 때마다 행의 수만큼 들었다. 처음 잰 시간은 노드당 99,067 ns였다. 명령은 노드당 1,539개뿐이라 명령 하나에
+64 ns였다. 행을 그 자리에서 읽게 고치자(aup의 커밋 e5a6209) 4,737 ns가 되었고, 답은 같다. aup, Pure, Vega의 다른 모듈은 하나도
+바뀌지 않는다. aup의 `make check`에 `rows_at`을 넣었다.
+
+**마찰 넷.**
+1. 저장 리스트 항목의 필드 여럿에 한 번에 대입하지 못한다(`boxes[k].x, boxes[k].y = …`). 네 줄로 썼다.
+2. 저장 리스트를 비우는 말이 없다. `while boxes.count > 0 { boxes.remove(boxes.last) }`로 썼다.
+3. 축을 고르는 길이 없다. 파이썬은 상자의 칸을 번호(`m`, `c`)로 고른다. 레코드의 필드는 번호로 고를 수 없어서 `if d.column { … }
+ else { … }`가 열두 번 든다.
+4. 리스트의 let은 한 줄에 하나다(다섯 줄). 줄 나누기의 마찰 3과 같다.
+
+**줄을 길게 한 것.** 116 대 54다. 닫는 괄호의 줄을 빼면 99다.
+- 레코드 둘(`Node`, `Box`)이 22줄이다. 파이썬은 튜플의 칸을 번호로 읽는다.
+- 9.7의 고리가 aup에서 24줄, 파이썬에서 10줄이다. 파이썬은 리스트 내포로 풀린 항목, 인자, 위반을 한 줄씩 짓는다.
+- justify의 첫 간격과 사이 간격이 파이썬에서 dict 한 줄씩, aup에서 `if … else if …` 한 줄씩이다(같다).
+
+**시간이 가는 곳.** 노드당 Pure 명령이 1,494개이고, 명령 하나에 약 3.2 ns가 든다. 앱을 단계마다 끊어 셈했다.
+- 아이 찾기(앞차례에서 부모가 그 노드인 행, 노드 수의 제곱의 반)와 상자 만들기가 159개다.
+- 항목의 리스트 다섯을 짓는 데 329개다.
+- 9.7의 고리가 658개(44%)다. 한 바퀴에 항목마다 세 번 돈다(쓴 크기와 인자, 나눔과 위반, 고정).
+- 남은 공간과 위치가 328개다.
+- 원본: `layout.py`(파이썬 참조), `layout_check.py`(맞음, 고정한 항목, 줄 수, 경로를 주면 트리를 쓴다), `layout_taffy`(Taffy
+ 0.15의 오라클과 `--time`, cargo), `layout_time.c`(Pure의 시간, 트리마다의 상자가 참조와 비트까지 같아야 잰다),
+ `2026-10-19-layout.jsonl`.
+
+**부분집합이 재지 않는 것.** 내용으로 정하는 크기(min-content 기여, 자동 최소 크기), wrap과 여러 줄, margin과 auto margin,
+min과 max가 남는다. Flexbox의 어려운 반은 크기가 아래에서 위로 올라오는 쪽인데, 이 부분집합은 크기가 위에서 오는 반만 잰다.
+그 반은 aup로 새 명령 없이 쓰이고, Taffy와 같다.
+
## 착수 기준 (다음 일)
- **바꿀 결정.** 계산의 핵을 설계한다(027). 위 표의 여덟 후보 가운데 무엇을 어떤 모양으로 받을지 정한다.