From 42bbeae8038be366a1ebc4fa83526f5cb95f35d6 Mon Sep 17 00:00:00 2001 From: xzawed <47019855+xzawed@users.noreply.github.com> Date: Sat, 26 Sep 2026 23:01:55 +0900 Subject: [PATCH 1/4] =?UTF-8?q?docs:=20SPEC=20for=20wide=20intermediates?= =?UTF-8?q?=20=E2=80=94=20exact=20pure=20arithmetic,=20narrowed=20only=20a?= =?UTF-8?q?t=20its=20root?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A draft for the user to confirm, direction included (DP-W1..W11). Measured before writing, on a local branch over today's main (helper check propagation included), four variants on the 625 rows and 48,000 random calls: - FULL (every tree): -3-though-the-answer-fits 59 -> 17, but a fast failure can become a hang and a later -2 can overtake a real overflow (measured); - PURE+CALL (leaves: variables, i32 constants, len, byte_len of a variable, and calls to loop-free infallible functions): 59 -> 20, with no hang and no overtaking — its leaves either cannot fail or fail only with -3, and always terminate; - no variant changes a correct status-0 answer, and every non--3 answer any variant gives equals the exact model. Grok plan: PURE over FULL, no reordering in PURE, narrow before `as f64` (an i64 above 2^53 would round into a new silent wrong answer). Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/STATUS.md | 6 +- docs/slices/README.md | 3 +- docs/slices/SPEC-wide-intermediates.md | 197 +++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 docs/slices/SPEC-wide-intermediates.md diff --git a/docs/STATUS.md b/docs/STATUS.md index 0acb53f..9a99329 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -235,9 +235,9 @@ > > **▶ 다음 세션 — 여기서 시작한다.** 순서대로: > -> 1. **다음 슬라이스 — 사용자 결정.** 후보의 정본은 `docs/slices/README.md`의 "다음 슬라이스" 절이다. 가장 가까운 것은 -> **넓은 중간값(WIDE)** 이다 — 참값이 드는데 `-3`인 행을 줄이지만 §9-71.4의 위험을 SPEC이 정해야 하고, 헬퍼 체크 전파 -> 뒤로 그 표를 다시 재야 한다(§9-72.7). 시험 구현은 로컬 브랜치 `exp/wide-local`에 있다(푸시 안 함). +> 1. **넓은 중간값 SPEC 확인 — 사용자 결정.** 초안은 `docs/slices/SPEC-wide-intermediates.md`이고 §4의 DP-W1~W11이 +> 확인을 기다린다(방향 포함). 헬퍼 체크 전파 뒤로 네 변형을 다시 쟀고, 권고는 **순수 트리만**(멈춤·앞지름 없이 참값이 +> 드는데 `-3`인 행 59 → 20)이다. 시험 구현은 로컬 브랜치 `exp/wide2-local`에 있다(푸시 안 함). > 2. **작은 항목**: C# 호스트(Phase 4) · LSP(Phase 3, 큰 일). > 3. **외부**: `X1`·`X2` — Delphi가 있는 CI 러너. 이 저장소 안에서는 닫히지 않는다. > diff --git a/docs/slices/README.md b/docs/slices/README.md index 24e46db..f1d7c2f 100644 --- a/docs/slices/README.md +++ b/docs/slices/README.md @@ -64,6 +64,7 @@ TDD 구현 → Grok 검증 → PR** 순서를 따른다(`CLAUDE.md` "개발 방 | [상수 선언 `const` · `export const`](SPEC-constants.md) | ✅ **구현 완료** (2026-09-26) | 이름 붙은 리터럴 — `export const`만 헤더(`ML__CONST_`)와 지문에 나가, 번호를 바꾼 모듈을 게이트가 거부한다 | #325 / #326·#327 | | [체크드 정수 산술](SPEC-checked-arithmetic.md) | ✅ **구현 완료** (2026-09-26) | `!` 본문의 i32 오버플로와 `f64 as i32`가 `ML_ST_OVERFLOW`(`-3`)로 실패한다 — 조용한 오답 161행 중 101이 status가 된다 | §9-69 / #334 · §9-70 | | [헬퍼 체크 전파](SPEC-helper-check-propagation.md) | ✅ **구현 완료** (2026-09-26) | `!` 함수가 부른 무한 함수(export 포함)의 산술도 검사하고 `-3`을 전파한다 — 조용한 오답 60 → 44 | §9-71 / #339 · §9-72 | +| [넓은 중간값](SPEC-wide-intermediates.md) | 🔨 **SPEC 초안 · 확인 대기** (2026-09-26) | `!` 문맥의 순수 산술 식을 정확히 계산하고 뿌리에서만 좁힌다 — 참값이 드는데 `-3`인 행 59 → 20, DP-W1~W11 확인 대기 | §9-71 · §9-72 / — | > **2026-09-25에 행을 한 줄로 접었다.** 접기 전의 서술은 [`../history/slices-index.md`](../history/slices-index.md)에 > 바이트 그대로 있다. **새 행도 한 줄로 쓴다** — 행당 400 B(`every_slice_index_row_is_one_line`)이고, 경위는 그 @@ -126,7 +127,7 @@ TDD 구현 → Grok 검증 → PR** 순서를 따른다(`CLAUDE.md` "개발 방 두 번째 호스트(C#, ROADMAP Phase 4), 그리고 ROADMAP Phase 2가 이름만 두고 설계가 없던 둘 — **인터페이스 정의 파일**(설계부터)과 **개발/배포 빌드 분리**(D13, `SECURITY.md` P1). *(2026-09-25 로드맵 검토에서 더했다 — Phase 2의 다섯 항목 중 이 둘이 이 목록에 없었다.)* -§7(§9-69, 12개 도메인 72규칙과 귀속 대조)이 더한 후보: **넓은 중간값**(626행으로 시험 구현했다 — 결정표는 §9-71.6, 위험은 §9-71.4), **`wrapping_*` 내장 함수**(무한 헬퍼라는 탈출구(DP-O7)가 §9-72로 사라졌다 — 필요가 측정되면), **i64**(§9-71: 옵트인이라 귀속 대조에서 51행이 빠졌다), **입력 문자열의 인코딩 계약**(닫힌 DP-S2의 재개 — `OPEN_QUESTIONS.md` Q17이 (A)로 열어 두었고 재검토 조건은 거기 있다), 비트 연산자, +§7(§9-69, 12개 도메인 72규칙과 귀속 대조)이 더한 후보: **넓은 중간값**(SPEC 초안이 있다, 위 색인), **`wrapping_*` 내장 함수**(무한 헬퍼라는 탈출구(DP-O7)가 §9-72로 사라졌다 — 필요가 측정되면), **i64**(§9-71: 옵트인이라 귀속 대조에서 51행이 빠졌다), **입력 문자열의 인코딩 계약**(닫힌 DP-S2의 재개 — `OPEN_QUESTIONS.md` Q17이 (A)로 열어 두었고 재검토 조건은 거기 있다), 비트 연산자, 지역 작업 배열, 수학 내장 함수, 문자 단위 길이. **struct는 §9-1이 강제되지 않는다고 결론냈다** — 고르려면 그 절을 먼저 읽는다(§9-69도 귀속 0으로 다시 확인했다). 배열도 같은 결론이었고(§9-9가 다섯 번째로), **입력은 그 사실을 SPEC 첫 절에 적은 채로 열렸다**. diff --git a/docs/slices/SPEC-wide-intermediates.md b/docs/slices/SPEC-wide-intermediates.md new file mode 100644 index 0000000..d55397f --- /dev/null +++ b/docs/slices/SPEC-wide-intermediates.md @@ -0,0 +1,197 @@ +# SPEC — 넓은 중간값 (순수 산술 식은 정확히 계산하고, 뿌리에서만 좁힌다) + +- **상태: 초안. 사용자 확인 전이며 제품 코드는 한 줄도 쓰지 않았다.** + - 이 문서가 묻는 것은 **§4의 결정 전부**다 — 방향(DP-W1)도 아직 확인받지 않았다. §9-72.7이 가장 가까운 후보로 적었고, + 사용자가 *"후속작업을 이어서"* 를 요청했다(2026-09-26). +- 선행: `SPEC-checked-arithmetic.md`(§2.1 — 검사 자리는 **연산자** · DP-O9 — 리터럴만의 오버플로는 실행 시 `-3`) · + `SPEC-helper-check-propagation.md`(검사된 사본도 fallible 문맥이다) +- 관련 결정: **D17**(음수 status 예약) · **D19**(C-emit 백엔드의 여지) +- 발단: §9-71.4(전체 WIDE의 위험) · §9-72.7(헬퍼 체크 전파 뒤로 다시 잴 것) + +--- + +## 0. 왜 — 측정부터 + +### 0.1 참값이 드는데 `-3` (E2) + +체크드 산술은 **연산자마다** 검사한다. 그래서 `qty * price / 100`처럼 중간곱만 넘치고 답은 i32에 드는 식이 `-3`이 된다 — +업무 규칙 표본에서 헬퍼 체크 전파 뒤로 **59행**(§9-72.3). 틀린 답은 아니지만, 맞는 답을 줄 수 있는 입력을 거절한다. + +### 0.2 네 변형을 시험 구현해 쟀다 (E2, 로컬 브랜치 `exp/wide2-local`, 머지 안 함) + +현재 `main`(헬퍼 체크 전파 포함) 위에 스위치로 넷을 구현했다: + +- **FULL** — `!` 본문(과 검사된 사본)의 모든 i32 산술 트리를 i64로 재고, 값이 i32로 가야 하는 자리에서만 검사한다. +- **PURE** — 잎이 **실패하지도 코드를 실행하지도 않는** 트리만: 변수 · i32 상수 · `len(xs)` · 변수의 `byte_len`. + 호출과 배열 읽기가 들어간 트리는 오늘처럼 연산자마다 검사한다. +- **PURE+CALL** — PURE에 **`while`이 없는 무한 함수의 호출**을 잎으로 더한 것(그 함수가 부르는 함수들에도 `while`이 없고, + 인자도 허용되는 잎). 그런 호출은 반드시 끝나고, 헬퍼 체크 전파 아래에서 낼 수 있는 실패는 `-3`뿐이다(§0.3). +- **PURE+INDEX** — PURE에 배열 읽기 잎을 더한 것. + +| 625행(메모 1 제외) | 맞음 | 조용한 오답 | 참값이 드는데 `-3` | +|---|---|---|---| +| 오늘 | 505 | 44 | 59 | +| FULL | 546 | 44 | **17** | +| PURE | 540 | 44 | 23 | +| **PURE+CALL** | 543 | 44 | **20** | +| PURE+INDEX | 543 | 44 | 20 | + +- **어느 변형도 맞던 status 0 답을 바꾸지 않았다.** 바뀐 행은 전부 §9-71에서 한 줄씩 판정한 답과 같다(모르는 행 0). +- PURE+CALL과 PURE+INDEX는 3행씩 더 얻지만 **다른 행**이다: 호출 잎이 든 식(`gross * grade_rate(grade) / 100`, records R1)과 + 배열 읽기 잎이 든 식(stateful R4). +- **무작위 48,000 호출**(함수 200 × 입력 80 × 시드 3, 정확한 정수 모델과 대조): 네 변형 모두 오늘 `-3`이 아니던 26,476 호출에서 + 차이 0. **넷 다 `-3`이 아닌 답은 전부 정확한 모델과 같다** — 새 오답을 만들지 않고, 모델보다 `-3`을 더 자주 낼 뿐이다 + (FULL 1,687 · PURE 7,814). 이 생성기에는 호출 잎이 없어 PURE+CALL은 PURE와, PURE+INDEX는 FULL과 같은 수를 낸다. + +### 0.3 FULL의 위험을 PURE(+CALL)는 구조로 피한다 (E2) + +§9-71.4가 FULL에서 잰 위험과, 같은 입력에서 PURE·PURE+CALL이 내는 답: + +| 위험 | FULL | PURE · PURE+CALL | +|---|---|---| +| 빨리 실패하던 입력이 **멈춘다** — `a*b + spin(n)`(`spin`은 끝나지 않는 `while`) | 20초 타임아웃에 죽였다 | 즉시 `-3`(오늘과 같다) — `spin`에는 `while`이 있어 잎이 아니다 | +| 뒤 피연산자의 실패가 앞의 진짜 오버플로를 **앞지른다** — `a*b + xs[5]` | `-2` | `-3`(오늘과 같다) | +| i64도 넘친다 — `a*b*c/(b*c)`, `b = c = i32::MAX` | `-3` | `-3` | +| `let` 추출이 답을 바꾼다 — `return a*b/b` 대 `let t = a*b return t/b` | 값 대 `-3` | 값 대 `-3` | + +**왜 구조로 피하는가.** PURE 트리의 잎은 실패하지도 코드를 실행하지도 않고, PURE+CALL이 더하는 호출 잎은 반드시 끝나며 +`-3`으로만 실패한다. 그래서 트리 안에서 검사를 미뤄도 **관측되는 차이가 없다**: 트리의 정확한 값이 i32에 들지 않는 진짜 +오버플로는 트리의 뿌리에서, 트리 밖의 어떤 것보다 먼저 `-3`이다(잎의 실패가 먼저 와도 같은 `-3`이다). Grok plan도 PURE에 +대해 같은 판정이다(Q2 NO). + +**`-3`이 다른 답으로 바뀌는 것은 정확한 값이 존재할 때뿐이다.** 트리가 더는 실패하지 않으므로 그다음에 실제로 실패하는 연산이 +답이 된다 — `xs[a*b/b]`의 범위 밖 인덱스나 `a*b/b + xs[5]`의 `xs[5]`(`-2`), `if a*b/b > K { fail E }`의 도메인 에러. +무작위 호출에서 그런 `-2`·도메인 답은 전부 정확한 모델과 같았다. + +### 0.4 크기 (E2) + +PURE로 잰 값: 예제 26개 `.text` 합 95,334 → 95,472바이트(+138), 모듈별 −123~+192, 6개 모듈만 바뀐다. PURE+CALL은 잎만 +늘어나므로 같은 방식으로 다시 잰다(§6 작업 1). + +--- + +## 1. 범위 + +**포함** — fallible 문맥(`!` 본문과 검사된 사본)에서: + +1. **순수 산술 트리**(§2.1)를 정확히(i64) 계산하고, 그 뿌리에서 한 번 i32로 좁힌다 — 들지 않으면 `-3`. +2. 두 쪽이 순수 트리인 i32 **비교**는 정확한 값끼리 비교한다. +3. `LANGUAGE.md`(평가 규칙 · DP-O9 · 중간값의 대가) · `HOST_ABI.md` · 테스트 · 실제 C 호스트. + +**미포함 (명시)** + +- 무한 본문 — 오늘처럼 연산자마다 wrap(§9-71: 넓히면 status 0 값이 바뀐다 — `(2147483647 + 1) / 2`). +- 배열 읽기나 `while`이 있는 함수의 호출이 든 트리(DP-W2) · 지역 변수의 폭(`let`은 i32로 좁힌다) · i64 타입 · C 백엔드 구현. +- `DECISIONS.md` — 바뀌는 것은 슬라이스 결정(체크드 산술 §2.1의 검사 자리, DP-O9의 서술)뿐이다. + +--- + +## 2. 계약 + +### 2.1 순수 산술 트리 (DP-W1, DP-W2) + +i32 `+ - * / %`와 단항 `-`로만 이어진 식 중, **잎이 모두 아래인 것**: + +- 변수(파라미터 · 지역) · i32 상수(리터럴과 `const`) · `len(xs)` · 변수의 `byte_len(s)` +- **`while`이 없는 무한 함수의 호출** — 그 함수가 (직접이든 여러 단계로든) 부르는 함수에도 `while`이 없고, 인자가 모두 + 이 목록의 잎이거나 순수 트리이거나 `bool`·`f64` 변수·상수일 때(DP-W2). 재귀는 금지이므로 그런 호출은 끝난다. 헬퍼 체크 + 전파 아래에서 그 호출이 낼 수 있는 실패는 `-3`뿐이다 — 무한 본문은 `fail`·`try`·인덱스를 쓸 수 없다. + +`while`이 있는 함수의 호출 · 내장 함수 호출 · 배열 읽기 · `f64 as i32` · 스팬은 잎이 될 수 없다 — 그런 것이 섞인 트리는 오늘처럼 +연산자마다 검사하고, 그 안의 **순수한 부분 트리**가 각자 넓게 계산된다. + +### 2.2 계산과 좁힘 (DP-W3, DP-W5) + +- 트리는 i64로, 왼쪽부터 계산한다. `/`·`%`의 0 제수는 `0`(DP-N4), `%`는 참 나머지(DP-O5). +- **i64 자체가 넘치면 `-3`** — "정확한 값"의 한계는 i64다(DP-W5). 코퍼스의 산술 뿌리 588개 중 정적 한계가 63비트를 넘는 것은 4개다 + (§9-71.4의 위험 검토가 컴파일러 IR로 센 값). +- 트리의 **뿌리**에서, 값을 받는 자리가 무엇이든(`let`·대입·`return`·인자·인덱스·`result`·`as string`·순수하지 않은 부모 + 연산) 한 번 i32로 좁힌다. 들지 않으면 `-3`. + +### 2.3 비교 (DP-W4) + +두 피연산자가 모두 순수 트리(적어도 한쪽은 산술)인 `< > <= >= == !=`는 정확한 값끼리 비교하고 좁히지 않는다 — +`a + b > 0`이 `a = i32::MAX, b = 1`에서 `true`(측정). 한쪽이 순수하지 않으면 오늘처럼. + +### 2.4 `as f64` — 먼저 좁힌다 (DP-W6) + +`(tree) as f64`는 트리를 **i32로 좁힌 뒤** 변환한다 — 들지 않으면 `-3`. 넓은 값을 그대로 f64로 바꾸면 2^53 위에서 반올림돼 +**새 조용한 오답**이 된다(Grok plan Q3). `(a*b/b) as f64`는 값, `(a*b) as f64`는 `-3`(측정). + +### 2.5 리터럴과 상수 — DP-O9를 다시 쓴다 (DP-W7) + +리터럴·상수만의 트리도 순수 트리다: `2147483647 + 1 - 1`은 `2147483647`, `2147483647 + 1`은 `-3`(측정). 체크드 산술의 +DP-O9(*"리터럴만의 오버플로는 실행 시 `-3`"*)는 *"정확한 값이 i32에 들지 않는 리터럴 식은 실행 시 `-3`"* 이 된다. +그 슬라이스 §5가 후보로 둔 **상수식 오버플로 진단**도 같은 의미를 따라야 한다. + +### 2.6 알려진 대가 (기록) + +- **`let` 추출이 답을 바꾼다** — `let`은 좁히는 자리다. `return a*b/b`는 값, `let t = a*b return t/b`는 `-3`. 지역 변수의 + 폭은 i32로 둔다(넓히면 언어 모델이 바뀐다). +- **같은 식이 문맥에 따라 다르다** — 무한 본문에서는 연산자마다 wrap한다. 체크드 산술이 이미 만든 차이가 넓어진다. +- **배열 읽기가 든 식은 좋아지지 않는다** — 표본에서 3행(PURE+CALL과 FULL의 차이, stateful R4). 배열 읽기를 잎으로 받으면 + (PURE+INDEX) 되찾지만 §0.3의 두 번째 위험 — 뒤의 `-2`가 진짜 오버플로를 앞지르는 것 — 을 다시 연다. +- **`while`이 있는 헬퍼를 부르는 식도 그대로다** — 멈춤 위험을 여는 유일한 잎이기 때문이다. + +### 2.7 바인딩 · 지문 · ABI (DP-W8) + +- `ML_ST_OVERFLOW` 선언 술어는 그대로다 — 순수 트리도 `-3`에 닿을 수 있다(과다 선언은 있어도 누락은 없다). +- 지문과 `ML_MODULE_ABI_VERSION`은 바뀌지 않는다(DP-O4와 같은 근거). 같은 소스를 다시 컴파일하면 `-3`이던 입력이 값이 + 될 수 있다 — 표시 없이, 문서에 적는다(DP-O8처럼). + +### 2.8 C 백엔드 (D19) — 요구만 적는다 (DP-W9) + +C로 내릴 때 순수 트리는 부호 있는 int64를 **넘침 검사와 함께** 계산해야 한다. C의 부호 있는 넘침은 미정의 동작이고, +MSVC 19.44는 ``·`__builtin_mul_overflow`를 받지 않는다(§9-71.4). 두 i32의 곱은 int64를 넘지 않으므로 대부분의 +트리는 정적 한계로 검사를 없앨 수 있다. C 백엔드는 없으므로 이 슬라이스는 요구만 `LANGUAGE.md`에 적는다. + +--- + +## 3. 수용 기준 (측정 가능) + +| # | 무엇 | 어떻게 잰다 | +|---|---|---| +| **A** | 순수 트리: 중간값만 넘치는 식이 정답(`a*b/b` · `(a+b)/2` · `a*2/2`), 정확한 값이 들지 않으면 `-3`, i64 초과는 `-3` | 오라클 값 | +| **B** | 순수하지 않은 트리는 오늘과 같다 — 호출·배열 읽기가 든 식의 `-3` 시점 | 오라클 값 | +| **C** | 멈춤 없음: `a*b + spin(n)`이 즉시 `-3` · 앞지름 없음: `a*b + xs[5]`가 `-3` | 오라클 값(타임아웃 가드) | +| **D** | 비교 · `as f64` · 리터럴(§2.3~2.5) | 오라클 값 | +| **E** | 무한 본문은 그대로 · 검사된 사본(헬퍼)에도 적용 | 오라클 값 | +| **F** | **실제 C 호스트가** 중간값만 넘치는 입력에서 정답을 받는다 | 수용 D(`host.c`) | +| **G** | 지문·ABI 불변 · 바인딩 불변 · 생성 Rust는 순수 트리가 있는 `!` 본문에서만 바뀐다 | 골든 | +| **H** | 문서 — `LANGUAGE.md`의 평가 규칙 · DP-O9 · 중간값 문장, `HOST_ABI.md` | `doc_claims` | +| **I** | 626행을 구현본으로 다시 재어 시험 구현(PURE+CALL)과 같은 표 | 측정 기록(가드 아님) | + +--- + +## 4. 설계 결정 (확인 요청) + +| DP | 결정 | 대안 | 근거 | +|---|---|---|---| +| **DP-W1** 방향·변형 | **넓힌다, 순수 트리만** — FULL이 아니라 | FULL · 하지 않음 | FULL 개선 41행 중 38을 멈춤·앞지름 없이 얻는다(PURE+CALL, §0.2~0.3). Grok plan(PURE 대 FULL): PURE | +| **DP-W2** 잎 집합 | 변수 · i32 상수 · `len` · 변수의 `byte_len` · **`while` 없는 무한 함수의 호출**(PURE+CALL) | 호출 제외(PURE, 3행 적다) · 배열 읽기 포함(PURE+INDEX) | 호출 잎은 끝나고 `-3`으로만 실패하므로 관측되는 차이가 없다. 배열 읽기의 `-2`는 순서가 관측된다 | +| **DP-W3** 좁히는 자리 | **순수 트리의 뿌리**, 소비자가 무엇이든 | 소비자별 규칙 | 한 규칙이 전부를 덮는다 | +| **DP-W4** 비교 | 두 쪽이 순수하면 **정확한 값끼리** | 먼저 좁힌다 | 비교는 i32 값을 만들지 않는다 | +| **DP-W5** 정확함의 한계 | **i64**, 넘치면 `-3` | i128 · 정적 한계 분석 | 코어 Rust에 i128이 있지만 C에는 없다. 한계를 넘는 뿌리는 코퍼스 588 중 4 | +| **DP-W6** `as f64` | **먼저 좁힌다** | 넓은 값을 변환 | 2^53 위 반올림 = 새 조용한 오답. Grok plan: NARROW | +| **DP-W7** DP-O9 | **정확한 값 기준으로 다시 쓴다** | 리터럴 식은 연산자마다 | 규칙이 하나로 남는다 | +| **DP-W8** 지문·ABI·바인딩 | **모두 불변**, 이주는 문서에 | 표시 · 버전 올림 | DP-O4·O8과 같은 근거 | +| **DP-W9** C 백엔드 | **요구만 적는다** | 지금 C 구현 방식을 정한다 | C 백엔드가 없다 | +| **DP-W10** 무한 본문 | **그대로**(연산자마다 wrap) | 넓힌다 | 넓히면 status 0 값이 바뀐다(§9-71, Grok plan) | +| **DP-W11** 수용 F | **기존 예제의 한 식**에서 C 호스트가 정답을 받는다(지문 불변) | 새 예제 | 생성 코드가 바뀌는 예제가 6개다(§0.4) | + +--- + +## 5. 범위 밖 / 미검증 (명시) + +- **배열 읽기가 든 식 · `while`이 있는 헬퍼를 부르는 식**(DP-W2의 대가 — 표본 3행) · **지역 변수 폭** · **i64 타입**. +- **성능** — i64 연산과 좁힘 검사. 크기는 §0.4, 속도는 재지 않았다. +- **C 백엔드의 실제 구현**(§2.8). + +--- + +## 6. 작업 분해 (확인 후) + +| 작업 | 수용 | 크기 | +|---|---|---| +| 1. codegen(순수 트리 판정 · i64 계산 · 뿌리 좁힘 · 비교) · 테스트 · 골든 · 문서 | A · B · C · D · E · G · H | 한 PR | +| 2. 실제 C 호스트 · 626행 재측정 · 슬라이스 닫기 | F · I | 한 PR | From c92b896a93e5b74400d99ee407ba98e7c4cf8151 Mon Sep 17 00:00:00 2001 From: xzawed <47019855+xzawed@users.noreply.github.com> Date: Sat, 26 Sep 2026 23:25:09 +0900 Subject: [PATCH 2/4] =?UTF-8?q?docs:=20SPEC-wide-intermediates=20=E2=80=94?= =?UTF-8?q?=20byte=5Flen=20leaves=20the=20leaf=20set,=20x/0=20joins=20the?= =?UTF-8?q?=20exact-value=20cases,=20scope=20and=20tasks=20line=20up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Grok verify: byte_len is a loop bounded only by the host's NUL contract, so deferring the check would run it on inputs where it never ran; dropping it from the leaves changes no answer on the 626 rows (measured). A runtime zero divisor under an exact product is 0 by DP-N4 (measured: today -3, PURE+CALL 0). Acceptance I gains a scope item, §1's boundary now matches §2.1 (pure subtrees are still widened), DP-W11 is in §6, and the variant table has its "other" column. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/slices/SPEC-wide-intermediates.md | 39 +++++++++++++++----------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/docs/slices/SPEC-wide-intermediates.md b/docs/slices/SPEC-wide-intermediates.md index d55397f..a7c6704 100644 --- a/docs/slices/SPEC-wide-intermediates.md +++ b/docs/slices/SPEC-wide-intermediates.md @@ -22,23 +22,26 @@ 현재 `main`(헬퍼 체크 전파 포함) 위에 스위치로 넷을 구현했다: - **FULL** — `!` 본문(과 검사된 사본)의 모든 i32 산술 트리를 i64로 재고, 값이 i32로 가야 하는 자리에서만 검사한다. -- **PURE** — 잎이 **실패하지도 코드를 실행하지도 않는** 트리만: 변수 · i32 상수 · `len(xs)` · 변수의 `byte_len`. +- **PURE** — 잎이 **실패하지도 코드를 실행하지도 않는** 트리만: 변수 · i32 상수 · `len(xs)`(측정 때는 변수의 `byte_len`도 — 아래). 호출과 배열 읽기가 들어간 트리는 오늘처럼 연산자마다 검사한다. - **PURE+CALL** — PURE에 **`while`이 없는 무한 함수의 호출**을 잎으로 더한 것(그 함수가 부르는 함수들에도 `while`이 없고, 인자도 허용되는 잎). 그런 호출은 반드시 끝나고, 헬퍼 체크 전파 아래에서 낼 수 있는 실패는 `-3`뿐이다(§0.3). - **PURE+INDEX** — PURE에 배열 읽기 잎을 더한 것. -| 625행(메모 1 제외) | 맞음 | 조용한 오답 | 참값이 드는데 `-3` | -|---|---|---|---| -| 오늘 | 505 | 44 | 59 | -| FULL | 546 | 44 | **17** | -| PURE | 540 | 44 | 23 | -| **PURE+CALL** | 543 | 44 | **20** | -| PURE+INDEX | 543 | 44 | 20 | +| 625행(메모 1 제외) | 맞음 | 조용한 오답 | 참값이 드는데 `-3` | 기타 | +|---|---|---|---|---| +| 오늘 | 505 | 44 | 59 | 17 | +| FULL | 546 | 44 | **17** | 18 | +| PURE | 540 | 44 | 23 | 18 | +| **PURE+CALL** | 543 | 44 | **20** | 18 | +| PURE+INDEX | 543 | 44 | 20 | 18 | + +기타의 +1은 규칙 불변식 밖 입력의 거절이 `-3`에서 도메인 에러 `3`으로 바뀐 한 행이다(stateful R1-oob, §9-71.3). - **어느 변형도 맞던 status 0 답을 바꾸지 않았다.** 바뀐 행은 전부 §9-71에서 한 줄씩 판정한 답과 같다(모르는 행 0). - PURE+CALL과 PURE+INDEX는 3행씩 더 얻지만 **다른 행**이다: 호출 잎이 든 식(`gross * grade_rate(grade) / 100`, records R1)과 배열 읽기 잎이 든 식(stateful R4). +- 위 변형들은 변수의 `byte_len`도 잎으로 받았다. 그것을 뺀 PURE+CALL도 626행 **전부 같은 답**이다(측정) — §2.1은 뺀다. - **무작위 48,000 호출**(함수 200 × 입력 80 × 시드 3, 정확한 정수 모델과 대조): 네 변형 모두 오늘 `-3`이 아니던 26,476 호출에서 차이 0. **넷 다 `-3`이 아닌 답은 전부 정확한 모델과 같다** — 새 오답을 만들지 않고, 모델보다 `-3`을 더 자주 낼 뿐이다 (FULL 1,687 · PURE 7,814). 이 생성기에는 호출 잎이 없어 PURE+CALL은 PURE와, PURE+INDEX는 FULL과 같은 수를 낸다. @@ -61,7 +64,8 @@ **`-3`이 다른 답으로 바뀌는 것은 정확한 값이 존재할 때뿐이다.** 트리가 더는 실패하지 않으므로 그다음에 실제로 실패하는 연산이 답이 된다 — `xs[a*b/b]`의 범위 밖 인덱스나 `a*b/b + xs[5]`의 `xs[5]`(`-2`), `if a*b/b > K { fail E }`의 도메인 에러. -무작위 호출에서 그런 `-2`·도메인 답은 전부 정확한 모델과 같았다. +무작위 호출에서 그런 `-2`·도메인 답은 전부 정확한 모델과 같았다. 제수가 런타임에 0인 `a*b/d`도 이 부류다 — 정확한 +`a*b`가 있으므로 DP-N4대로 `0`이다(측정: 오늘 `-3`, PURE+CALL `0`). ### 0.4 크기 (E2) @@ -77,11 +81,13 @@ PURE로 잰 값: 예제 26개 `.text` 합 95,334 → 95,472바이트(+138), 모 1. **순수 산술 트리**(§2.1)를 정확히(i64) 계산하고, 그 뿌리에서 한 번 i32로 좁힌다 — 들지 않으면 `-3`. 2. 두 쪽이 순수 트리인 i32 **비교**는 정확한 값끼리 비교한다. 3. `LANGUAGE.md`(평가 규칙 · DP-O9 · 중간값의 대가) · `HOST_ABI.md` · 테스트 · 실제 C 호스트. +4. §0.2의 626행을 구현본으로 다시 재어 시험 구현과 대조한다(기록이지 가드가 아니다). **미포함 (명시)** - 무한 본문 — 오늘처럼 연산자마다 wrap(§9-71: 넓히면 status 0 값이 바뀐다 — `(2147483647 + 1) / 2`). -- 배열 읽기나 `while`이 있는 함수의 호출이 든 트리(DP-W2) · 지역 변수의 폭(`let`은 i32로 좁힌다) · i64 타입 · C 백엔드 구현. +- §2.1의 잎이 아닌 것(배열 읽기 · `while`이 있는 함수의 호출 등)을 넘어 넓히는 것 — 그런 것이 든 식에서도 그 안의 순수한 + 부분 트리는 넓힌다(DP-W2) · 지역 변수의 폭(`let`은 i32로 좁힌다) · i64 타입 · C 백엔드 구현. - `DECISIONS.md` — 바뀌는 것은 슬라이스 결정(체크드 산술 §2.1의 검사 자리, DP-O9의 서술)뿐이다. --- @@ -92,13 +98,14 @@ PURE로 잰 값: 예제 26개 `.text` 합 95,334 → 95,472바이트(+138), 모 i32 `+ - * / %`와 단항 `-`로만 이어진 식 중, **잎이 모두 아래인 것**: -- 변수(파라미터 · 지역) · i32 상수(리터럴과 `const`) · `len(xs)` · 변수의 `byte_len(s)` +- 변수(파라미터 · 지역) · i32 상수(리터럴과 `const`) · `len(xs)` - **`while`이 없는 무한 함수의 호출** — 그 함수가 (직접이든 여러 단계로든) 부르는 함수에도 `while`이 없고, 인자가 모두 이 목록의 잎이거나 순수 트리이거나 `bool`·`f64` 변수·상수일 때(DP-W2). 재귀는 금지이므로 그런 호출은 끝난다. 헬퍼 체크 전파 아래에서 그 호출이 낼 수 있는 실패는 `-3`뿐이다 — 무한 본문은 `fail`·`try`·인덱스를 쓸 수 없다. -`while`이 있는 함수의 호출 · 내장 함수 호출 · 배열 읽기 · `f64 as i32` · 스팬은 잎이 될 수 없다 — 그런 것이 섞인 트리는 오늘처럼 -연산자마다 검사하고, 그 안의 **순수한 부분 트리**가 각자 넓게 계산된다. +`while`이 있는 함수의 호출 · `byte_len`을 포함한 내장 함수 호출 · 배열 읽기 · `f64 as i32` · 스팬은 잎이 될 수 없다 — 그런 것이 섞인 트리는 오늘처럼 +연산자마다 검사하고, 그 안의 **순수한 부분 트리**가 각자 넓게 계산된다. `byte_len`은 NUL까지 도는 반복이라 끝나는 것이 +호스트의 NUL 계약에만 기댄다 — 검사를 미루면 오늘은 돌지 않던 입력에서 돌게 되므로 뺀다(빼도 표본의 답은 같다, §0.2). ### 2.2 계산과 좁힘 (DP-W3, DP-W5) @@ -167,8 +174,8 @@ MSVC 19.44는 ``·`__builtin_mul_overflow`를 받지 않는다(§9- | DP | 결정 | 대안 | 근거 | |---|---|---|---| -| **DP-W1** 방향·변형 | **넓힌다, 순수 트리만** — FULL이 아니라 | FULL · 하지 않음 | FULL 개선 41행 중 38을 멈춤·앞지름 없이 얻는다(PURE+CALL, §0.2~0.3). Grok plan(PURE 대 FULL): PURE | -| **DP-W2** 잎 집합 | 변수 · i32 상수 · `len` · 변수의 `byte_len` · **`while` 없는 무한 함수의 호출**(PURE+CALL) | 호출 제외(PURE, 3행 적다) · 배열 읽기 포함(PURE+INDEX) | 호출 잎은 끝나고 `-3`으로만 실패하므로 관측되는 차이가 없다. 배열 읽기의 `-2`는 순서가 관측된다 | +| **DP-W1** 방향·변형 | **넓힌다, 순수 트리만** — FULL이 아니라 | FULL · 하지 않음 | FULL 개선 41행 중 38을 멈춤·앞지름 없이 얻는다(PURE+CALL, §0.2~0.3). Grok plan은 PURE 대 FULL 질문에 PURE | +| **DP-W2** 잎 집합 | 변수 · i32 상수 · `len` · **`while` 없는 무한 함수의 호출**(PURE+CALL, `byte_len` 제외) | 호출 제외(PURE, 3행 적다) · 배열 읽기 포함(PURE+INDEX) | 호출 잎은 끝나고 `-3`으로만 실패하므로 관측되는 차이가 없다. 배열 읽기의 `-2`는 순서가 관측되고, `byte_len`은 호스트 계약에만 기대 끝난다(빼도 표본의 답이 같다) | | **DP-W3** 좁히는 자리 | **순수 트리의 뿌리**, 소비자가 무엇이든 | 소비자별 규칙 | 한 규칙이 전부를 덮는다 | | **DP-W4** 비교 | 두 쪽이 순수하면 **정확한 값끼리** | 먼저 좁힌다 | 비교는 i32 값을 만들지 않는다 | | **DP-W5** 정확함의 한계 | **i64**, 넘치면 `-3` | i128 · 정적 한계 분석 | 코어 Rust에 i128이 있지만 C에는 없다. 한계를 넘는 뿌리는 코퍼스 588 중 4 | @@ -194,4 +201,4 @@ MSVC 19.44는 ``·`__builtin_mul_overflow`를 받지 않는다(§9- | 작업 | 수용 | 크기 | |---|---|---| | 1. codegen(순수 트리 판정 · i64 계산 · 뿌리 좁힘 · 비교) · 테스트 · 골든 · 문서 | A · B · C · D · E · G · H | 한 PR | -| 2. 실제 C 호스트 · 626행 재측정 · 슬라이스 닫기 | F · I | 한 PR | +| 2. 기존 예제의 한 식에서 실제 C 호스트가 정답을 받는다(DP-W11) · 626행 재측정 · 슬라이스 닫기 | F · I | 한 PR | From b8f5fcae758d4a80630713deb532894d3318f489 Mon Sep 17 00:00:00 2001 From: xzawed <47019855+xzawed@users.noreply.github.com> Date: Sat, 26 Sep 2026 23:45:32 +0900 Subject: [PATCH 3/4] =?UTF-8?q?docs:=20SPEC-wide-intermediates=20=E2=80=94?= =?UTF-8?q?=20a=20call=20leaf=20takes=20no=20string,=20and=20"before"=20me?= =?UTF-8?q?ans=20before=20what=20is=20evaluated=20after?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Grok verify: a call leaf with a string argument runs `==`/`byte_len` in the callee, loops that end only by the host's NUL contract — the same reason byte_len left the leaves. Dropping them changes no answer on the 626 rows (measured). "Before anything outside the tree" was too wide: `xs[5] + a*b` is -2 today and after, because the index is evaluated first; the sentence now says "before anything evaluated after the tree", and acceptance B names the three ordering cases (measured: xs[5]+a*b -2/-2, a*b+xs[5] -3/-3, a*b/b+xs[5] -3/-2). Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/slices/SPEC-wide-intermediates.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/slices/SPEC-wide-intermediates.md b/docs/slices/SPEC-wide-intermediates.md index a7c6704..f9afde4 100644 --- a/docs/slices/SPEC-wide-intermediates.md +++ b/docs/slices/SPEC-wide-intermediates.md @@ -41,7 +41,8 @@ - **어느 변형도 맞던 status 0 답을 바꾸지 않았다.** 바뀐 행은 전부 §9-71에서 한 줄씩 판정한 답과 같다(모르는 행 0). - PURE+CALL과 PURE+INDEX는 3행씩 더 얻지만 **다른 행**이다: 호출 잎이 든 식(`gross * grade_rate(grade) / 100`, records R1)과 배열 읽기 잎이 든 식(stateful R4). -- 위 변형들은 변수의 `byte_len`도 잎으로 받았다. 그것을 뺀 PURE+CALL도 626행 **전부 같은 답**이다(측정) — §2.1은 뺀다. +- 위 변형들은 변수의 `byte_len`과 문자열 인자를 받는 호출도 잎으로 받았다. 둘을 뺀 PURE+CALL(§2.1이 권고하는 잎)도 626행 + **전부 같은 답**이다(측정). - **무작위 48,000 호출**(함수 200 × 입력 80 × 시드 3, 정확한 정수 모델과 대조): 네 변형 모두 오늘 `-3`이 아니던 26,476 호출에서 차이 0. **넷 다 `-3`이 아닌 답은 전부 정확한 모델과 같다** — 새 오답을 만들지 않고, 모델보다 `-3`을 더 자주 낼 뿐이다 (FULL 1,687 · PURE 7,814). 이 생성기에는 호출 잎이 없어 PURE+CALL은 PURE와, PURE+INDEX는 FULL과 같은 수를 낸다. @@ -59,7 +60,8 @@ **왜 구조로 피하는가.** PURE 트리의 잎은 실패하지도 코드를 실행하지도 않고, PURE+CALL이 더하는 호출 잎은 반드시 끝나며 `-3`으로만 실패한다. 그래서 트리 안에서 검사를 미뤄도 **관측되는 차이가 없다**: 트리의 정확한 값이 i32에 들지 않는 진짜 -오버플로는 트리의 뿌리에서, 트리 밖의 어떤 것보다 먼저 `-3`이다(잎의 실패가 먼저 와도 같은 `-3`이다). Grok plan도 PURE에 +오버플로는 트리의 뿌리에서, **트리보다 뒤에 평가되는** 어떤 것보다 먼저 `-3`이다(잎의 실패가 먼저 와도 같은 `-3`이다). +트리보다 앞에서 평가되는 것은 오늘도 먼저 돈다 — `xs[5] + a*b`는 오늘도 PURE+CALL에서도 `-2`다(왼쪽부터). Grok plan도 PURE에 대해 같은 판정이다(Q2 NO). **`-3`이 다른 답으로 바뀌는 것은 정확한 값이 존재할 때뿐이다.** 트리가 더는 실패하지 않으므로 그다음에 실제로 실패하는 연산이 @@ -100,7 +102,9 @@ i32 `+ - * / %`와 단항 `-`로만 이어진 식 중, **잎이 모두 아래인 - 변수(파라미터 · 지역) · i32 상수(리터럴과 `const`) · `len(xs)` - **`while`이 없는 무한 함수의 호출** — 그 함수가 (직접이든 여러 단계로든) 부르는 함수에도 `while`이 없고, 인자가 모두 - 이 목록의 잎이거나 순수 트리이거나 `bool`·`f64` 변수·상수일 때(DP-W2). 재귀는 금지이므로 그런 호출은 끝난다. 헬퍼 체크 + 이 목록의 잎이거나 순수 트리이거나 `bool`·`f64` 변수·상수이며 **문자열 인자가 없을 때**(DP-W2). 문자열을 받는 함수의 + `==`·`byte_len`은 NUL까지 돌아 호스트의 NUL 계약에 기대기 때문이다(아래 `byte_len`과 같은 이유). 배열 인자는 된다 — + 무한 본문은 원소를 읽을 수 없고 `len`은 길이를 읽을 뿐이다. 재귀는 금지이므로 그런 호출은 끝난다. 헬퍼 체크 전파 아래에서 그 호출이 낼 수 있는 실패는 `-3`뿐이다 — 무한 본문은 `fail`·`try`·인덱스를 쓸 수 없다. `while`이 있는 함수의 호출 · `byte_len`을 포함한 내장 함수 호출 · 배열 읽기 · `f64 as i32` · 스팬은 잎이 될 수 없다 — 그런 것이 섞인 트리는 오늘처럼 @@ -159,7 +163,7 @@ MSVC 19.44는 ``·`__builtin_mul_overflow`를 받지 않는다(§9- | # | 무엇 | 어떻게 잰다 | |---|---|---| | **A** | 순수 트리: 중간값만 넘치는 식이 정답(`a*b/b` · `(a+b)/2` · `a*2/2`), 정확한 값이 들지 않으면 `-3`, i64 초과는 `-3` | 오라클 값 | -| **B** | 순수하지 않은 트리는 오늘과 같다 — 호출·배열 읽기가 든 식의 `-3` 시점 | 오라클 값 | +| **B** | 순수하지 않은 연산자는 오늘처럼 그 자리에서 검사되고, 그 안의 순수 부분 트리만 정확히 계산된다 — `a*b + xs[5]`는 `-3`, `a*b/b + xs[5]`는 `-2`, 문자열을 받는 호출·`while`이 있는 호출이 든 식도 같다 | 오라클 값 | | **C** | 멈춤 없음: `a*b + spin(n)`이 즉시 `-3` · 앞지름 없음: `a*b + xs[5]`가 `-3` | 오라클 값(타임아웃 가드) | | **D** | 비교 · `as f64` · 리터럴(§2.3~2.5) | 오라클 값 | | **E** | 무한 본문은 그대로 · 검사된 사본(헬퍼)에도 적용 | 오라클 값 | @@ -175,7 +179,7 @@ MSVC 19.44는 ``·`__builtin_mul_overflow`를 받지 않는다(§9- | DP | 결정 | 대안 | 근거 | |---|---|---|---| | **DP-W1** 방향·변형 | **넓힌다, 순수 트리만** — FULL이 아니라 | FULL · 하지 않음 | FULL 개선 41행 중 38을 멈춤·앞지름 없이 얻는다(PURE+CALL, §0.2~0.3). Grok plan은 PURE 대 FULL 질문에 PURE | -| **DP-W2** 잎 집합 | 변수 · i32 상수 · `len` · **`while` 없는 무한 함수의 호출**(PURE+CALL, `byte_len` 제외) | 호출 제외(PURE, 3행 적다) · 배열 읽기 포함(PURE+INDEX) | 호출 잎은 끝나고 `-3`으로만 실패하므로 관측되는 차이가 없다. 배열 읽기의 `-2`는 순서가 관측되고, `byte_len`은 호스트 계약에만 기대 끝난다(빼도 표본의 답이 같다) | +| **DP-W2** 잎 집합 | 변수 · i32 상수 · `len` · **`while` 없고 문자열 인자 없는 무한 함수의 호출**(PURE+CALL, `byte_len` 제외) | 호출 제외(PURE, 3행 적다) · 배열 읽기 포함(PURE+INDEX) | 호출 잎은 끝나고 `-3`으로만 실패하므로 관측되는 차이가 없다. 배열 읽기의 `-2`는 순서가 관측되고, `byte_len`은 호스트 계약에만 기대 끝난다(빼도 표본의 답이 같다) | | **DP-W3** 좁히는 자리 | **순수 트리의 뿌리**, 소비자가 무엇이든 | 소비자별 규칙 | 한 규칙이 전부를 덮는다 | | **DP-W4** 비교 | 두 쪽이 순수하면 **정확한 값끼리** | 먼저 좁힌다 | 비교는 i32 값을 만들지 않는다 | | **DP-W5** 정확함의 한계 | **i64**, 넘치면 `-3` | i128 · 정적 한계 분석 | 코어 Rust에 i128이 있지만 C에는 없다. 한계를 넘는 뿌리는 코퍼스 588 중 4 | From b22ed9e6ec9127f39c4a37b0c1cdcd56a2f2c876 Mon Sep 17 00:00:00 2001 From: xzawed <47019855+xzawed@users.noreply.github.com> Date: Sat, 26 Sep 2026 23:52:47 +0900 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20SPEC-wide-intermediates=20=C2=A72.1?= =?UTF-8?q?=20names=20span=20comparison=20among=20what=20an=20infallible?= =?UTF-8?q?=20body=20cannot=20do?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/slices/SPEC-wide-intermediates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/slices/SPEC-wide-intermediates.md b/docs/slices/SPEC-wide-intermediates.md index f9afde4..e2f98ed 100644 --- a/docs/slices/SPEC-wide-intermediates.md +++ b/docs/slices/SPEC-wide-intermediates.md @@ -105,7 +105,7 @@ i32 `+ - * / %`와 단항 `-`로만 이어진 식 중, **잎이 모두 아래인 이 목록의 잎이거나 순수 트리이거나 `bool`·`f64` 변수·상수이며 **문자열 인자가 없을 때**(DP-W2). 문자열을 받는 함수의 `==`·`byte_len`은 NUL까지 돌아 호스트의 NUL 계약에 기대기 때문이다(아래 `byte_len`과 같은 이유). 배열 인자는 된다 — 무한 본문은 원소를 읽을 수 없고 `len`은 길이를 읽을 뿐이다. 재귀는 금지이므로 그런 호출은 끝난다. 헬퍼 체크 - 전파 아래에서 그 호출이 낼 수 있는 실패는 `-3`뿐이다 — 무한 본문은 `fail`·`try`·인덱스를 쓸 수 없다. + 전파 아래에서 그 호출이 낼 수 있는 실패는 `-3`뿐이다 — 무한 본문은 `fail`·`try`·인덱스·스팬 비교를 쓸 수 없다. `while`이 있는 함수의 호출 · `byte_len`을 포함한 내장 함수 호출 · 배열 읽기 · `f64 as i32` · 스팬은 잎이 될 수 없다 — 그런 것이 섞인 트리는 오늘처럼 연산자마다 검사하고, 그 안의 **순수한 부분 트리**가 각자 넓게 계산된다. `byte_len`은 NUL까지 도는 반복이라 끝나는 것이