aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/research/140-spec-suite.md44
-rw-r--r--research/experiments/044-specs/2026-10-19-frictions.jsonl11
-rw-r--r--research/experiments/044-specs/rto_check.py4
-rw-r--r--tests/check.py2
-rw-r--r--tests/specs/rto/README.md2
5 files changed, 56 insertions, 7 deletions
diff --git a/docs/research/140-spec-suite.md b/docs/research/140-spec-suite.md
index 3ddb709..9cbe5c2 100644
--- a/docs/research/140-spec-suite.md
+++ b/docs/research/140-spec-suite.md
@@ -51,11 +51,11 @@
| 2 | SHA-256(FIPS 180-4) | 해시 | 32비트 낱말, 회전, xor | NIST CAVP의 SHA 벡터 | 우회로만 될 것으로 보인다(써 보지 않음) | 썼다: NIST 벡터를 세 실행 길에서 지난다(1단계) |
| 3 | inflate(RFC 1951) | 압축 | 비트 읽기, 허프만 표, 뒤 참조 | zlib로 압축한 말뭉치를 풀어 원본과 비교 | 못 쓴다(바이트, 비트) | 썼다: zlib 스트림 640개, 손상 240개가 파이썬 참조와 같다. 우회 없음, 줄 1.5배, C의 102배(아래) |
| 4 | JSON(RFC 8259) → 트리와 질의 | 깊이를 모르는 데이터 | 글자 읽기, 중첩, 재귀, 갈래 | JSONTestSuite | 못 쓴다(글자, 중첩, 재귀, 갈래) | 썼다: JSONTestSuite와 무작위 문서가 파이썬과 같다(2a–2c) |
-| 5 | UTF-8과 줄 나누기(UAX #14) | 텍스트 | 바이트, 코드 포인트, 속성 표 | Unicode의 `LineBreakTest.txt` | 못 쓴다(글자) | 썼다: LineBreakTest.txt 16,672줄 모두와 무작위 2,000줄이 맞다. 줄 1.8배, C의 199배(아래) |
+| 5 | UTF-8과 줄 나누기(UAX #14) | 텍스트 | 바이트, 코드 포인트, 속성 표 | Unicode의 `LineBreakTest.txt` | 못 쓴다(글자) | 썼다: LineBreakTest.txt 16,672줄 모두와 무작위 2,000줄이 맞다. 줄 1.7배(마찰을 걷어 낸 뒤), 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)의 결과와 대조 | 못 쓴다(중첩, 재귀) | 썼다(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배(아래) |
+| 8 | 상자 배치(flex 같은) | UI 개념 | 중첩, 재귀 | 기존 배치 구현(Yoga, Taffy)의 결과와 대조 | 못 쓴다(중첩, 재귀) | 썼다(Flexbox의 부분집합): 무작위 트리 2,000개가 Taffy와 0.01 안, 파이썬 참조와 비트까지 같다. 줄 2.0배(마찰을 걷어 낸 뒤), Taffy의 7.4배(아래) |
+| 9 | HTTP/1.1 요청을 패킷마다(RFC 9112) | 스트림의 상태 기계 | 바이트, 이벤트를 넘는 상태 | 경계를 아무 데서나 자른 요청 모음 | 못 쓴다(바이트). 지금은 `serve/`의 C다 | 썼다: 스트림 200개를 어디서 잘라도 h11과 같다. 우회 없음(리스트 값의 대입을 aup에 넣었다), 줄 1.8배, 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 밖) |
@@ -921,6 +921,44 @@
min과 max가 남는다. Flexbox의 어려운 반은 크기가 아래에서 위로 올라오는 쪽인데, 이 부분집합은 크기가 위에서 오는 반만 잰다.
그 반은 aup로 새 명령 없이 쓰이고, Taffy와 같다.
+## aup의 마찰을 걷어 내고 다시 잼 (2026-10-19)
+
+열한 명세를 쓰며 적은 마찰 가운데 코어를 바꾸지 않고 풀리는 것을 aup에서 풀었다. 새 명령은 없다. 바꾸기 전에 컴파일되던 aup 소스
+81개(aup, Pure, Vega)의 모듈은 모두 바이트까지 같다.
+- **리스트의 let 여럿을 한 줄에.** `let codes, cls, eff: list of number`. 이름마다 그 레코드의 리스트 하나다(UAX #14의 마찰 3, 배치의
+ 마찰 4).
+- **대상 여럿.** `boxes[k].x, boxes[k].y = at, top`처럼 대상에 항목의 필드와 위치가 섞여도 된다(배치의 마찰 1). 값을 먼저 모두 셈하고,
+ 대상마다 왼쪽부터 준다(파이썬과 같다). 값이 앞 대상이 바꾸는 것을 읽을 수 없으면 숨은 let 없이 곧바로 준다. 대상의 이름, fn의 부름,
+ 바뀌는 리스트의 인덱스나 for의 항목을 읽는 값이 있으면 숨은 let을 거친다. 처음에는 늘 숨은 let을 거쳐 배치의 명령이 2.1%
+ 늘었다(노드당 1,525.9). 곧은 길을 넣은 뒤 1,492.8이고, 손으로 쓴 것은 1,493.9다.
+- **리스트에 리스트 값.** `trailers, j = fields(d, c + 2)`, `kept = ts`, `boxes = []`. 저장되는 리스트에도 된다(HTTP의 남은 우회, 배치의
+ 마찰 2). 비우고 채우는 고리는 손으로 쓴 것과 같은 명령이다.
+- **`abs(x)`와 `tick.period`.** RFC 6298의 마찰 1과 2다. G를 `every`와 fn에 두 번 쓰지 않는다.
+- **수를 만나는 16진은 수.** `base == 0x2010`(UAX #14의 마찰 1). 2^53을 넘으면 거절한다. 따로 서거나 i64를 만나면 예전처럼 i64다.
+- **쓰다가 찾은 결함.** 리스트의 let을 셋 이상 한 줄에 쓰면 문장이 고리가 되어 컴파일러가 끝나지 않았다(줄 나누기의 아홉 리스트를
+ 두 줄로 쓰다 찾았다). aup의 `frictions` 시험이 이제 넷을 쓴다.
+
+| 명세 | 줄(닫는 괄호) 전 → 뒤 | 파이썬 | 바꾼 것 | 단위당 명령 전 → 뒤 | 단위당 ns 전 → 뒤 |
+|---|---|---|---|---|---|
+| inflate | 121(15) → 120(15) | 81 | 리스트의 let 둘을 한 줄에 | 191.2(모듈이 같다) | 같다 |
+| DNS | 106(18) → 106(18) | 72 | `records = []`, `0xc000 + s.at` | 49.9 → 49.8 | 221.0 → 220.0 |
+| HTTP/1.1 | 148(21) → 146(21) | 79 | `trailers, j = fields(…)` | 61.5 → 61.6 | 270.6 → 270.6 |
+| UAX #14 | 176(19) → 169(19) | 99 | 리스트의 let 아홉을 두 줄에, `0x2010`, `0x25CC` | 1,774(모듈이 같다) | 같다 |
+| RFC 6298 | 56(11) → 56(11) | 30 | `abs(srtt - r)`, `tick.period` | 확인 192.7 → 191.7 | — |
+| 상자 배치 | 116(17) → 110(17) | 54 | 리스트의 let 넷을 한 줄에, 상자와 뿌리의 대상 묶음, `boxes = []` | 1,493.9 → 1,492.8 | 4,756 → 4,647 |
+
+- **줄.** 파이썬 대비 1.47–2.15배에서 1.47–2.04배로 줄었다. 같아진 것도 있다: DNS와 RFC 6298은 줄은 같고 읽기만 나아졌다(`while`
+ 한 줄이 `records = []`, `max(srtt - r, r - srtt)`가 `abs(srtt - r)`, G가 한 곳).
+- **명령과 시간.** 모듈이 바뀐 넷의 명령은 ±0.2% 안이다. 배치의 시간 −2.3%는 명령이 같으므로 자리 잡음 범위로 본다(세 쌍을 번갈아
+ 잰 중앙값).
+- **세 실행 길.** 여섯 명세의 검사가 새 앱으로 모두 지난다: dnspython의 메시지 400개와 손상 300개, h11의 스트림 200개(모든 자름),
+ RFC 6298의 사건 241,612개, 배치의 트리 2,000개(Taffy의 0.01 안, 참조와 비트까지). inflate와 UAX #14는 모듈이 같다. Pure의
+ `module_specs` 아홉이 지난다.
+- **남은 마찰.**
+ - 코어의 것: 보기에서 블록 fn 부르기(DNS), 레코드 하나인 상태, 커서 자리의 글자.
+ - aup에 두는 것: 축을 필드 번호로 고르기(배치의 마찰 3), `number`나 `send` 같은 이름. 이름은 타입과 말의 자리를 지키려고 그대로 둔다.
+- 원본: `2026-10-19-frictions.jsonl`(검사, 전과 뒤의 명령과 시간, 줄). aup의 설계 002 "140의 마찰을 걷어 냄".
+
## 착수 기준 (다음 일)
- **바꿀 결정.** 계산의 핵을 설계한다(027). 위 표의 여덟 후보 가운데 무엇을 어떤 모양으로 받을지 정한다.
diff --git a/research/experiments/044-specs/2026-10-19-frictions.jsonl b/research/experiments/044-specs/2026-10-19-frictions.jsonl
new file mode 100644
index 0000000..a5668a2
--- /dev/null
+++ b/research/experiments/044-specs/2026-10-19-frictions.jsonl
@@ -0,0 +1,11 @@
+{"what": "check (the reworked app)", "messages": 400, "bytes": 148582, "records": 3466, "right": {"batches": 400, "rounds": 400, "all": 400}, "paths_same": true, "damaged": 300, "damaged_refused": 185, "damaged_agreed": 300, "aup_lines": 106, "aup_brace_lines": 18, "python_lines": 72}
+{"what": "check (the reworked app)", "streams": 200, "bytes": 132623, "requests": 578, "right": {"whole": 200, "random": 200, "bytes": 50}, "paths": {"batches": 200, "rounds": 200, "all": 200}, "paths_same": true, "damaged": 200, "damaged_refused": 35, "damaged_agreed": 200, "aup_lines": 146, "aup_brace_lines": 21, "python_lines": 79}
+{"what": "check (the reworked app)", "scenarios": 20, "events": 241612, "timeouts": 156, "right": {"batches": 241612, "rounds": 241612, "all": 241612}, "paths_same": true, "costs": {"10": {"ticks": 218070, "timeouts": 78, "ticks_a_timeout": 2795.8, "late_mean_ms": 4.27, "late_most_ms": 9.875}, "100": {"ticks": 21968, "timeouts": 78, "ticks_a_timeout": 281.6, "late_mean_ms": 49.23, "late_most_ms": 99.0}}, "aup_lines": 56, "aup_brace_lines": 11, "python_lines": 30}
+{"what": "check (the reworked app)", "trees": 2000, "nodes": 29388, "refused": {}, "floored": {"items": 7659, "trees": 1399}, "taffy_right": 2000, "taffy_worst": 8.33333334639974e-05, "right": {"batches": 2000, "rounds": 2000, "all": 2000}, "paths_same": true, "aup_lines": 110, "aup_brace_lines": 17, "python_lines": 54}
+{"what": "time, before and after", "spec": "dns", "tool": "dns_time.c, three pairs in turns, medians (PURE_JIT=0)", "old": {"ns": 221.014, "instructions": 7409761, "per": 49.9}, "new": {"ns": 220.02, "instructions": 7405497, "per": 49.8}}
+{"what": "time, before and after", "spec": "http", "tool": "http_time.c, three pairs in turns, medians (PURE_JIT=0)", "old": {"ns": 270.576, "instructions": 6074414, "per": 61.5}, "new": {"ns": 270.579, "instructions": 6086742, "per": 61.6}}
+{"what": "time, before and after", "spec": "layout", "tool": "layout_time.c, three pairs in turns, medians (PURE_JIT=0)", "old": {"ns": 4755.6, "instructions": 43902659, "per": 1493.9}, "new": {"ns": 4646.5, "instructions": 43869283, "per": 1492.8}}
+{"what": "work per event, before and after", "spec": "rto", "tool": "pure-run work on tests/specs/rto (10 ms)", "old": {"total": 54201, "tick": 25.0, "transmit": 12.0, "ack": 192.7}, "new": {"total": 54182, "tick": 25.0, "transmit": 12.0, "ack": 191.7}}
+{"what": "modules the same, before and after", "specs": ["inflate", "linebreak"], "note": "their instructions and time are as before"}
+{"what": "lines (code, closing braces), before and after", "inflate": [[121, 15], [120, 15]], "dns": [[106, 18], [106, 18]], "http": [[148, 21], [146, 21]], "linebreak": [[176, 19], [169, 19]], "rto": [[56, 11], [56, 11]], "layout": [[116, 17], [110, 17]]}
+{"what": "layout boxes through lets", "note": "aup 39d4a02..6463228: a value each into a let first", "instructions_per_node": 1525.9, "straight": 1492.8, "hand_written": 1493.9}
diff --git a/research/experiments/044-specs/rto_check.py b/research/experiments/044-specs/rto_check.py
index be41ffb..1cc30bf 100644
--- a/research/experiments/044-specs/rto_check.py
+++ b/research/experiments/044-specs/rto_check.py
@@ -112,9 +112,9 @@ if __name__ == '__main__':
with tempfile.TemporaryDirectory() as work:
source = open(os.path.join(aup, 'examples', 'rto.aup')).read()
for period in (10, 100):
- module = os.path.join(work, f'rto-{period}.pmod') # its tick and its G the scenario's period
+ module = os.path.join(work, f'rto-{period}.pmod') # its tick the scenario's period
with open(os.path.join(work, f'rto-{period}.aup'), 'w') as f:
- f.write(source.replace('every 10 ms', f'every {period} ms').replace('fn PERIOD = 10 ', f'fn PERIOD = {period} '))
+ f.write(source.replace('every 10 ms', f'every {period} ms')) # G, tick.period, follows
subprocess.run([os.path.join(aup, 'build', 'aup'), 'build', os.path.join(work, f'rto-{period}.aup'), '-o', module], check=True)
ticks = timeouts = 0
lateness = []
diff --git a/tests/check.py b/tests/check.py
index dd64e03..cab0347 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -1971,7 +1971,7 @@ def module_specs(aup, pure, work):
source, events = (EXAMPLES / 'rto.aup').read_text(), 0
for scenario in json.loads((pure / 'tests/specs/rto/scenarios.json').read_text()):
period = scenario['period']
- (work / 'rto.aup').write_text(source.replace('every 10 ms', f'every {period} ms').replace('fn PERIOD = 10 ', f'fn PERIOD = {period} '))
+ (work / 'rto.aup').write_text(source.replace('every 10 ms', f'every {period} ms')) # G is tick.period
program = work / 'rto.pmod'
compile_module(aup, work / 'rto.aup', program)
given = ''.join(f'time {t}\n' + ('tick tick\n' if kind == 'tick' else f"message {kind} {struct.pack('<I', seq).hex()}\n")
diff --git a/tests/specs/rto/README.md b/tests/specs/rto/README.md
index e090586..9cc82a2 100644
--- a/tests/specs/rto/README.md
+++ b/tests/specs/rto/README.md
@@ -3,5 +3,5 @@
`scenarios.json` holds one scenario (seed 2, 20 segments, five timeouts) of `research/experiments/044-specs/rto_check.py`
at tick periods of 10 and 100 ms: the events an app is given (time in ms, kind, segment) and, after each, the state the
model of the same rules (`rto.py`) has (SRTT, RTTVAR, RTO, deadline, segments outstanding, retransmissions).
-`tests/check.py`'s `module_specs` gives the events to aup's `examples/rto.aup` (its tick and G made the period's) on
+`tests/check.py`'s `module_specs` gives the events to aup's `examples/rto.aup` (its tick made the period's; G is `tick.period`) on
Pure's three paths.