VM견고성 — 안전 프레임 회수 + STR풀 동적 성장 (2026-06-20)
개요
crownyc VM(C, 15K줄)의 두 장기 함정을
직접·왕복검증으로 해결.
브리프:
crowny-orchestrate/briefs/VM견고성.md, SSOT:
CrownyDoc/projects/2026-06-19-한선씨-현황정리-4세션착수.md.
- ① 안전 프레임 회수: FRAME_LEAVE 가드 조건부 회수 → 스칼라 재귀 누수 제거, 추출기 불변.
- ③ STR풀 wedge 근본수정: 고정캡(2M슬롯/32MB) → GC 후 부족 시 동적 2배 성장(절대상한 64M/1GB까지).
- ④ 무회귀 하니스 2종 신설(골든 + 두-바이너리 A/B).
- ② 캡 동적화: STR풀은 동적화 완료(③). 아레나는 양단(배열↑/맵↓) 설계상 단순 상향 무효 → ①이 상향 압력 완화. (아래 분석)
무엇을 했는지
① 안전 프레임 회수 (crownyc.c case 79 FRAME_LEAVE)
- 함정: 기존 FRAME_LEAVE는 mem_count를 절대 안 줄임(누수). 과거 무조건 회수(
mem_count=frame_ptr) 시도는 반환 배열/누수 자식프레임을 파괴 → 추출기 손상(되돌림 이력, feedback_crownyc_frame_reclaim_unsafe).
- 해법(핵심 통찰): 진입 시 기록한 프레임 끝(
fstack_mc[fsp])과 mem_count가 정확히 같을 때만 회수.
c if (mem_count == frame_end && frame_start >= 10000) mem_count = frame_start;
같음 = 이 프레임 진입 후 그 위에 배열/맵/누수자식
아무것도 할당 안 됨 증명 → 지역 슬롯(스칼라뿐, 그 주소를 가리키는 핸들 존재 불가)은 전부 죽음 → 안전. 무언가 할당됐으면
mem_count > frame_end라 회수 안 함(보수적) →
배열반환 무거운 재귀(추출기) 절대 불변.
- 연쇄 효과: 순수 스칼라 재귀는 자식이 자기 프레임 회수→부모도 끝위치 일치→연쇄 회수로 mem_count 평탄.
③ STR풀 동적 성장 (crownyc.c str_pool/str_new/str_gc)
- 함정: 풀 고정캡 도달 + GC 회수 0 → str_new -1 무한 스핀 = 장수서버 wedge(게이트웨이 전역 503). 기존 완화=2M상향+50연속 exit(42) 자가재기동(상태 손실).
- 해법:
str_pool/str_offsets/str_lengths/str_marks/remap를 정적배열→동적(malloc/realloc) 전환.
str_new: 캡 도달 → ①GC → ②부족하면
str_grow_slots/
str_grow_pool(2배, 절대상한까지) → ③그래도 부족(진짜 폭주)이면 기존 exit(42) 백스톱. 정당한 대용량 작업셋은 wedge 대신 성장으로 생존.
슬롯배열 성장=외부 보유 포인터 없음(항상 안전). 풀바이트 성장=realloc 이동 가능하나 연결연산이 str_new 전 로컬 buf 복사 → 안전(스트레스 검증).
- 발견: 보수적 문자열 GC가 [0,mem_count] 스캔 → ①의 프레임 누수가 죽은 핸들을 살아있다 오판해 GC 회수율을 떨어뜨려 wedge를 악화시킴. ①이 이 거짓생존을 완화 = ①③ 상호보강.
검증 (직접·왕복, 서브에이전트 과대PASS 배제)
하니스 (tests/, 재사용)
golden_harness.sh capture|check — 각 tests/*.한선의 직접실행 + 추출기체인(추출기→hanseonc_rpn→crownyc) 출력을 골든으로 캡처/대조.
ab_vm.sh <base> <new> <high> <rpn> — 두 crownyc 바이너리 출력 즉시 A/B(골든 staleness·동시 19세션 무관). VM 변경 시 권장.
- macOS엔 coreutils
timeout 없음 → perl alarm 래퍼 내장.
① 결과
- 추출기 체인 01_산술 =
10 -4 42 5 -2 불변(과거 회수가 5→1로 깨던 바로 그 케이스).
- tests 63/63 A/B 바이트동일. 1개(삼진커널골든회귀)는 내부 핸들주소 출력만 낮게 시프트(=회수 증거),
통과10/실패0 ALL GREEN 유지.
- 스트레스
tests/프레임회수스트레스.한선(합800 ×3만회): 구VM [VM경고] 35001건(48M천장 클램프) → 신VM 0건. 둘다 정답 320400.
③ 결과
스트레스 tests/STR풀성장스트레스.한선(살아있는 300문자열):
초기캡 축소 빌드(-DSTR_INIT_COUNT=64 -DSTR_INIT_POOL=4096): 검증통과 300/300, exit0, 경고0 — 64슬롯에 300 live = 성장 없이는 불가능 → 성장 정확 동작 증명(realloc 이동 무손상).
성장 불가 빌드(hard==init=구 고정캡 거동): [STR]풀한계 1617건, 출력 손상(-2/-3). → 동적성장이 실제 손상/wedge를 막음을 대조 증명.
③ A/B(①only vs ①+③) 정상 스위트 동일 66 / 상이 0 무회귀 ✓(성장 미발화, static→pointer 리팩터 행동보존).배포
- 정본 빌드+ad-hoc 재서명+원자교체:
cc -O2 -o tmp crownyc.c -lm -framework Security -framework CoreFoundation; codesign -s - tmp; mv -f tmp crownyc; codesign -s - crownyc (cp 손상/코드사인 캐시 함정 회피).
- 백업:
crownyc.c.bak.vmgyeon_20260620.
관련 파일
crownyc.c — case 79(①), str_init/str_grow_slots/str_grow_pool/str_new/str_gc(③), mem_init 훅
tests/golden_harness.sh tests/ab_vm.sh — 무회귀 하니스(④)
tests/프레임회수스트레스.한선 tests/STR풀성장스트레스.한선 — 증명 스트레스(한선씨 동반)
잔여 이슈
- ① 배열/맵 반환 프레임은 여전히 누수(보수적 회수, 의도적). 장수서버는 마커 회수(730/731) 병행.
- ② 아레나 캡: 양단 설계(배열 mem_count↑ 10000~, 맵 heap_ptr↓ 47.9M~)라 MEM_MAX 상향은 heap_ptr 위 빈공간만 늘려 무효. 유효 레버는 ①(상향압력 완화) + heap_ptr 시작점 동반상향(=더 큰 고정아레나, RSS비용). 근본은 단일방향 아레나+자동 mark-compact 재설계(대규모, 별도 세션).
- 맵 729캡 grow는 별도 진행중(crownyc-next, feedback_crownyc_map_729_cap).