# 024 — 기존 작성물을 네이티브 앱 상태·저장 경로에 연결 2026-09-23. Linux AArch64, Zig 0.15.2 / GCC 14.2.0. 첫 공개 체크포인트 진행 중이다. 이번 실행 호스트는 터미널이며 네이티브 창은 아직 없다. ## 바뀐 경계 기존 Workbench의 계산 실행기는 전역 `engine` 하나를 사용했다. Wasm 인스턴스마다 메모리가 분리되는 브라우저에서는 여러 작성물을 실행할 수 있었지만, 같은 코드를 네이티브 라이브러리로 붙이면 앱마다 독립된 상태를 제공하지 못했다. 실제 계산을 `engine.zig`의 명시적인 `Engine` 인스턴스로 옮겼다. 기존 `core.zig`는 브라우저 ABI와 회귀 검사를 유지하는 얇은 연결부다. 네이티브 `native.zig`는 호출자가 제공한 정렬된 메모리에 인스턴스를 초기화하고 C 함수로 접근한다. 네이티브 모듈에는 기본 VM 인스턴스, 할당기, 파일·소켓·스레드가 없다. 실행 정의의 준비·실패·복원·평가 기록도 해당 인스턴스에 속한다. 이 계산 모듈은 **앱이 선택하는 기존의 제한된 scalar 실행기**다. 모든 Pure 실행 영역에 자동으로 포함하는 새 공통 VM이 아니다. 작업 접수·수명·대여·예산을 맡는 C 코어는 변경하지 않았다. ## 작성에서 네이티브 실행까지 새 문법이나 별도 파서를 만들지 않았다. `native/prepare.mjs`가 기존 `compiler.mjs`로 `.pure`를 준비하고 읽기 전용 C 데이터로 출력한다. 상태·입력 이름, 공유 규칙의 바인딩, 대상 라우팅, 표시 식이 그대로 들어간다. 문자열은 UTF-8 바이트의 C escape로 출력해 소스 문자열이 C 코드가 되지 않게 한다. 이 데이터와 실행 모듈·호스트를 링크해 독립 실행 파일을 만든다. Node·Zig·C 컴파일러는 **빌드 도구**다. 만들어진 실행물은 Node·브라우저·Wasm 없이 구동한다. 실행 시 소스 파서나 AST를 보관하지 않는다. 다만 연산은 기존 준비 명령을 해석하며, 앱의 각 연산을 기계어로 직접 생성하는 AOT 최적화 컴파일러를 구현한 것은 아니다. 현재 작성 변경은 다시 빌드해서 반영한다. 실시간 소스 편집·교체는 아직 네이티브 호스트에 연결하지 않았다. ## 하나의 실행 코어를 통과하는 앱 경로 `native/app.c`는 준비된 앱의 입력과 상태를 기존 C 실행 코어에 연결한다. ```text 입력 → 코어의 접수·예산·작업 슬롯 → 선택한 계산 인스턴스 → 상태/표시 값 상태 스냅샷 → 코어의 내부 결과 대여 → 조건부 바이트 저장 저장된 바이트의 매핑 → 코어의 내부 결과 대여 → 검증 후 상태 복원 ``` 저장과 복원은 기존 `pure_runtime_forward`를 사용한다. 전달을 위한 두 번째 본문 버퍼나 소켓 직렬화를 추가하지 않는다. 단, **현재 상태를 스냅샷으로 만들 때는 복사한다.** 이후 입력이 상태를 바꾸어도 저장할 내용이 달라지지 않도록 44 B의 헤더와 상태당 8 B를 별도 소유한다. 같은 데이터의 두 작업이 유지되는 동안 각 작업의 크레딧을 계산하므로 이를 물리 메모리로 합산하지 않는다. 예제의 상태 25개는 스냅샷 244 B다. 버전 충돌은 상대 값을 덮어쓰지 않고 현재 앱 상태도 바꾸지 않는다. 복원은 정의 식별자·상태 수·길이·유한한 값을 검사한 뒤 전체 정의와 값을 함께 설치한다. 실패하면 기존 앱 상태와 채택한 저장 버전은 유지한다. 초기값과 현재 값은 별개라 복원 후 `reset`은 작성된 초기값으로 돌아간다. 정의 식별자는 빌드 도구가 만든 SHA-256이다. 의미만 같은 다른 소스나 위치 정보가 달라진 정의를 자동으로 호환 처리하지 않으며, 정의가 다르면 복원을 거절한다. 이는 인증이나 서명이 아니다. 현재 스냅샷은 브라우저 Workbench의 JSON 프로젝트 형식과 별개이며 변환기는 미구현이다. 수치 표현도 현재 IEEE binary64 경계에 한정한다. 모든 Pure 데이터를 이 배치로 강제하지 않는다. 포인터는 입력 그룹이 아닌 실제 대상마다 소유자를 가진다. 같은 라우팅 입력의 두 대상을 동시에 조작하고 하나만 취소할 수 있다. 다른 소유자의 이동·해제는 거절한다. 키 입력은 여러 출처가 눌러도 첫 down과 마지막 up만 작성 규칙에 전달한다. 진행 중인 입력이 있으면 저장· 복원을 거절한다. 이벤트의 계산 실패는 상태 쓰기를 원자적으로 되돌리며 실제 입력 해제와 구분한다. 네이티브 명령 인터페이스의 오류 표현·무시 정책 전체가 브라우저 UI와 같다는 주장은 아니다. ## micro 비용 일반 네이티브 라이브러리 설정은 기본 panic 보고·심볼 해석과 compiler-rt 아카이브를 포함했다. 이 선택 모듈에는 그 전체 기능이 필요하지 않았다. `ReleaseSafe`의 범위·오버플로 검사를 유지하고 예상하지 못한 내부 불변식 위반은 trap으로 중단하도록 바꿨다. 정상적인 작성 오류는 기존 상태 코드로 반환한다. 실패를 무시하거나 안전 검사를 꺼서 줄인 것은 아니다. 내부 버그 발생 시 이 모듈이 자체 스택 추적을 출력하지 않는 비용 절충은 남으며, 네이티브 크래시 격리도 아니다. 필요하지 않은 compiler-rt 전체를 포함하지 않은 뒤 실제 외부 심볼은 `memcpy`, `memset`뿐이다. 초기 아카이브 수치는 동결 자료에 보관했다. 링크 전 아카이브 합을 최종 실행물 크기 감소율로 해석하지 않는다. | 범위 | 확인한 비용 | |---|---:| | 공통 C 실행 코어의 GNU `size text` | 5,932 B, 이전과 동일 | | 선택한 scalar 라이브러리의 `size text` | 33,020 B | | scalar 라이브러리의 data/BSS | 0 B / 0 B | | 앱별 scalar 인스턴스 | 20,064 B, 정렬 8 B | | 앱 컨트롤러 | 928 B, 실행 코어와 작업 슬롯 2개 포함 | | 앱 입력 값 | 32 B | | 상태 25개의 저장 스냅샷 | 244 B | | 라우팅 예제 전체 CLI의 text/data/BSS 합 | 64,883 B | | 같은 실행물의 심볼 제거 후 ELF 파일 | 68,000 B | 계산 인스턴스의 20,064 B에는 최대 용량의 준비 정의·교체 작업 공간·상태·평가 기록이 들어간다. 읽기 전용 C 준비 데이터도 실행물에 존재한다. 현재 배열의 고정 예약과 정의 복사 비용을 없앴다고 하지 않는다. 입력·스냅샷의 호스트 할당, 스택, libc·로더·커널·페이지·유휴/피크 RSS는 위 구조체 수치와 별개다. 현재 ELF의 직접 동적 의존성은 `libc.so.6`이며 전체 의존 메모리 비용을 측정한 결과가 아니다. 코어 크기 검사는 그대로 통과한다. ## 실제로 확인한 범위 [동결된 근거](../../prototypes/workbench/results/2026-09-23-native-instances/)에 다음을 보관했다. - 기존 Zig 의미 검사 37개, 브라우저/Wasm 및 Node CLI 회귀 검사 87개 통과. - 기존 카운터·토글·프레임 계산·드래그·공유 대상·숫자 입력과 빈 정의·계산 실패·문자열 경계 등 작성물 9개의 입력 156단계에서 네이티브와 Wasm의 상태·표시 값 일치. - 각 네이티브 작성물을 저장한 뒤 새 프로세스에서 같은 정의로 복원해 값과 표시 식을 확인. - 같은 프로세스의 인스턴스 3개에서 상태·준비 실패·평가 설정·입력 소유권·예산이 섞이지 않음을 확인. - 실제 저장소의 버전 충돌, 다른 정의, 비유한 상태 복원 거절과 모든 작업·크레딧 회수 확인. - 일반 C 호스트와 ASan/UBSan C 호스트에서 위 네이티브 경로 통과. Zig 오브젝트는 둘 다 `ReleaseSafe`이며 ASan으로 계측된 Zig 빌드라고 주장하지 않는다. 동시 소유 스레드 간 실행, 네이티브 창·IME·접근성·크기 변경, macOS 실사용은 이번 검증 범위에 없다. 기존 scalar 한도(상태 32, 입력 8, 표시 24, 명령 512)도 유지된다. 대규모 앱 전체를 이 한도에 맞추라는 뜻이 아니라 이전 작성 모델을 실제 네이티브·저장 경로로 연결한 중간 구현이다. 전체 체크포인트의 일반 앱 표현·비동기 효과·보안 RPC·실제 화면·규모 검증은 계속 남아 있다.